一枚Javer对Ruby的吐槽

公司收购了个项目,技术都很老,Web用的Ruby on Rails,前端还在用jQuery,后台定时任务用Java(用的技术也都很老)。自己以前没有接触过Ruby,只是听几个朋友大学里玩过这玩意儿,所以对我来说Ruby就是一门全新的语言。我个人是非常抵触学一门新语言的,Java都还没吃透就并行学Ruby,只会分散深入学习Java的精力。但没办法谁让公司抽到我,还是好好学吧!

1、安装Ruby

Windows上傻瓜式安装Ruby就不赘述了(有RubyInstaller会点下一步就行),其他操作系统都可以用系统相应的包依赖管理器安装Ruby(Linux上的yum、apt-get,Mac上的homebrew)。但是这些包管理器通常只能安装yum源中已有的二进制包,如果你想要的Ruby版本没有在对应操作系统已编译的二进制包,那就只能下载源码自行编译了。虽然通常只需三个命令就能完成编译,但是多个Ruby之间版本的切换也是个问题,这也是小型脚本语言的痛。像Java大版本内不会有语法上改变,小版本也只是修复些bug,切换版本重新编译费时费劲,但Node.js和Ruby这样小巧而且版本间差异相对较大的语言,就需要运行环境的版本管理工具了。

大多数Rubyist都使用Ruby管理工具管理多个Ruby,官方列出来的Ruby管理工具有四个,最常用也是最好用的就是RVM(和Node.js对应有NVM)。安装RVM只需要从https://get.rvm.io下载一个rvm-installer的Bash脚本即可。ruby-installer支持默认支持两个版本masterstable,需要稳定版直接curl -sSL https://get.rvm.io | bash -s stable一个管道命令解决。

nvm$HOME/.nvm一样rvm也会在用户目录下创建.rvm目录保存各个版本的Ruby

安装完RVM后,使用以下命令就可以安装各种版本的Ruby了。

rvm install INTERPRETER[-VERSION] OPTIONS

其中INTERPRETER指的是不同语言实现的Ruby解释器,没有指定默认就是CRuby,也可以指定Java实现的jruby或.Net实现的ironruby等,使用rvm list known可以查看可选的Ruby版本。

切换Ruby版本只需用rvm use INTERPRETER[-VERSION]命令。

2、Ruby周边

gem

RubyGems是Ruby的包管理工具(和Java的Maven,JS的npm类似),从Ruby1.8开始就称为了Ruby默认的包管理工具。使用上和npm大体一致,详情可以参看官方文档

bundler

如果说gem是npm的阉割版,bundler就是为了补全被阉割的那部分。gem的.gemspec虽然定义了包本身的信息,但是不能像npm install一样安装并管理这个包依赖的其他包。Bundler需要定义一个Gemfile的文件,这个文件中可以定义应用依赖的gems的兼容版本(和npm的package.json类似)。

所以Bundler官网标题就说Bundler是管理Ruby的gems最好的方式。

Gemfile与.gemspec的区别详请参考这篇文章

Ruby on Rails

Ruby on Rails是一个基于Ruby的MVC框架,和express.js类似,也正是因为这个框架使得默默无闻的Ruby一夜崛起。也是这个框架最早提出Convention Over Configuration的概念,后来其他语言的框架也纷纷效仿,如今口口相传的SpringBoot也深受它的影响。

3、 初学Ruby的20条经验

1、 Ruby和JS、Python一样是解释性脚本语言,使用源文件即可执行,按照惯例源文件以.rb为后缀。和Python的强制缩进不通,Ruby的缩进并不重要,但是为了代码可读性必须得缩进。

2、 不像JS中0undefinednull""等值都能被转成false,在Ruby中除了falsenil这两个保留字,其他全为true。

3、 Ruby方法调用时,括号是可选的。方法中如果没有明确的return,则返回最后一条语句的结果。

foobar
foobar()
foobar(a,b,c)
foobar a,b,c

def my_method(a,b)
    puts a+b
    a*b     # 这条语句的结果将会作为返回值
end

详情参考官方文档

4、 Ruby除了支持+-*/%等算数运算符外,还支持**幂运算符,但是不支持++--运算符。这点和Python一致。在C和Java中%表示取余操作,但Python和Ruby中%表示求模运算。

取 余 : r e m ( a , b ) = a − i n t ( a / b ) ∗ a 取余:rem(a, b) = a-int(a/b)*a rem(a,b)=aint(a/b)a
求 模 : m o d ( a , b ) = a − f l o o r ( a / b ) ∗ a 求模: mod(a, b) = a-floor(a/b)*a mod(a,b)=afloor(a/b)a

// Java 结果的正负与被除数一致
System.out.println(5 % 3);     //  2
System.out.println(-5 % 3);    // -2
System.out.println(5 % -3);    //  2
System.out.println(-5 % -3);   // -2

// Ruby 结果的正负与除数一致
puts (5 % 3)     # prints  2  
puts (-5 % 3)    # prints  1  
puts (5 % -3)    # prints -1  
puts (-5 % -3)   # prints -2

// Python
print (5 % 3)     # prints  2  
print (-5 % 3)    # prints  1  
print (5 % -3)    # prints -1  
print (-5 % -3)   # prints -2  

5、 Ruby中一切皆对象,不像Java还有几个基本类型。Ruby中数值类型也是对象。Ruby中Float是双精度浮点型(也就是Java里的Double)。并且Ruby原生就支持BigNum(也就是Java中的BigInteger),所以再也不用担心算数溢出的问题了。并且Ruby 2.4之后Fixnum和Bignum统一为Integer了。

# Before Ruby 2.4
1.class         #=> Fixnum
(2 ** 62).class #=> Bignum

# Ruby 2.4
1.class         #=> Integer
(2 ** 62).class #=> Integer

有关Fixnum和Bignum更多细节可参考这篇文章,除了Integer和Float这两种常用的数值类型,Numeric还有Rational(有理数)和Complex(复数)两个子类,用处不大这里不过多讨论。

6、 Ruby的变量命名会影响变量的作用域:

  • 局部变量的命名规则和C系列语言命名一样,字母下划线开头数字字母下划线的组合;

    使用local_variables方法可以查看当前作用域内的局部变量

  • 在局部变量的开头加上@,表示这个变量是实例对象的属性变量(@sign,@_,@Counter);

    使用instance_variables方法可以查看对象的实例变量

  • 在局部变量的开头加上@@,表示类变量;

    使用class_variables方法可以查看类变量

  • 在局部变量的开头加上$,表示全局变量;

    使用global_variables方法可以查看全局变量

  • 按照Ruby的约定,全大写的变量是常量,Ruby允许修改常量,但是会报警告。

    PI = 3.141592653  # 第一次定义常量
    PI = 3.14         # 修改常量
    warning: already initialized constant PI
    warning: previous definition of PI was here
    puts PI           # 3.14
    

7、 Ruby中字符串可以使用单引号也可以使用双引号,但是单引号中只会对\'\\进行转义,双引号中除了会对各种转移字符转义外,还支持变量插值。

puts '\\hello \'\n\' world\\'  
> \hello '\n' world\

puts "\\hello \'\n\' world\\"
> \hello '
  ' world\
    
name = 'holmofy'
puts "hello #{name}"
> hello holmofy

8、 Ruby中每声明一个字符串都会创建一个新的字符串对象,而且在Ruby中要判断两个对象是否是同一个对象不能简单的用==,对象的.eql?方法和==都只会判断对象的内容是否相同,.equal?方法才能判断是否为同一个对象。

s1 = "hello world"
s2 = "hello world"
puts s1.object_id    # 70254227595080
puts s2.object_id    # 70254227587240
puts s1 == s1        # true
puts s1.eql? s2      # true
puts s1.equal? s2    # false

9、 Ruby中声明一个字符串数组,可以使用%w快捷方式:

names1 = [ 'ann', 'richard', 'william', 'susan', 'pat' ]  
puts names1[0] # ann  
puts names1[3] # susan  

# 使用%w快捷方式声明数组,其中w就是word的缩写
# 其实等价于'ann richard william susan pat'.split(' ')
names2 = %w{  ann richard william susan pat }  
puts names2[0] # ann  
puts names2[3] # susan  

更多字符串快捷方式可以参考这篇文章

10、 Java中不支持多行文本字符串,但是JS(反点)和Python(三个连续引号)这些脚本语言中都支持,Ruby中可以使用Here Document。Ruby中追加字符串可以使用<<操作符。

expected_result = <<HEREDOC
This would contain specially formatted text.
That might span many lines
HEREDOC

a = "hello"
a << "world"
puts a       # hello world

11、 对象的Mutable和Immutable一直是令人争议的话题,可变意味着不安全难以维护,不可变意味着效率低下。比如在Java、JS和Python中字符串对象都是不可变的,在JS和Python中字符串拼接通常效率极低,在Java中有StringBuilder解决这个问题,但是Ruby为了解决这些问题,在String中提供了两组方法,其中方法名的末尾加上!表示该方法会修改对象内容。!符号作为约定被用在了标准库和第三方库中。

a = "hello"
puts a.object_id   # 70254210667380
b = a.reverse
puts a             # hello
puts b             # olleh
puts a.object_id   # 70254210667380
puts b.object_id   # 70254210630260

a = "hello"
puts a.object_id   # 70254227510440
b = a.reverse!
puts a   # olleh
puts b   # olleh
puts a.object_id   # 70254227510440
puts b.object_id   # 70254227510440

12、 方法名除了有!后缀表示会修改对象或入参的内容,Ruby还有?后缀表示这个方法返回truefalse=后缀表示这个方法是对象属性的setter方法,可以使用赋值运算符修改对象属性。

puts "hello".start_with? "hell"               # true

13、 Ruby内建的正则表达式对象和JS一样,使用/.../双斜杠创建正则字面量,也可以使用%r{...}或Regexp的构造函数。

# 和JS不同的是,//斜杠中间可以使用变量插值
place = "東京都"
m = /#{place}/.match("Go to 東京都")
    #=> #
puts m[0]  # 東京都
# 和其他语言一样m[0]是整个正则匹配组,如果正则中有其他捕获组索引从1开始

14、 由于Ruby中每声明一个字符串都会创建一个字符串对象,并且字符串对象是可变的,所以Ruby还提供了一个与字符串类似,但不可变且全局唯一的Symbol类对象。

puts "a".object_id     # 70107393092020
puts "a".object_id     # 70107393041480
puts :a.object_id      # 372328
puts :a.object_id      # 372328
puts (:"a").object_id  # 372328

Symbol包括变量名、方法名、类名,每声明一个变量,方法,类都会在符号表中记录下相应的名字,使用Symbol.all_symbols方法可以查看当前符号表里所有的符号。

# 不要尝试使用Symbol.all_symbols.include? :new_symbol的方式检测符号是否存在
# 因为当这条语句解析时:new_symbol已经添加到符号表中了,执行的结果肯定是true
# 所以这里要转成字符串
curr_symbols = Symbol.all_symbols.map {|symbol| symbol.to_s}
puts curr_symbols.include? '_any_variable_name_'  # false
_any_variable_name_ = 'this is new variable'      # 这里可以是新变量、方法、类的声明
curr_symbols = Symbol.all_symbols.map {|symbol| symbol.to_s}
puts curr_symbols.include? '_any_variable_name_'  # true

15、 Ruby内建了Array、Hash、Range三种数据结构。可以简单地与Java中的ArrayList、HashMap、Guava中的Range等同。

arr = [0, 1, 2]
puts arr[0]             # 访问数组下标从0开始
arr = [1, "two", 3.0]   # 可以存放不通类型的对象
puts arr[-1]            # 索引值可以为负数,最后一个元素索引为-1


grades = { "Jane Doe" => 10, "Jim Doe" => 6 }
puts grades["Jane Doe"]     # 10
# 使用Symbol作为Key
options = { :font_size => 10, :font_family => "Arial" }
options[:font_size]         # 10
# 使用Symbol作为Key可以简写成下面这种形式
options = { font_size: 10, font_family: "Arial" }


# (start..end)语法表示Range对象,两个点包括end,三个点不包括end
(-1..-5).to_a      #=> []
(-5..-1).to_a      #=> [-5, -4, -3, -2, -1]
('a'..'e').to_a    #=> ["a", "b", "c", "d", "e"]
('a'...'e').to_a   #=> ["a", "b", "c", "d"]

Ruby2.6开始支持Endless Range,即end值可以不指定,默认到无穷

更多内容参考Array、Hash、Range的官方文档

16、 调用一个方法就相当于发一个消息,Ruby的传参还是比较复杂的

my_method(arg1, arg2)
my_method arg1, arg2
# 但是同时调用两个方法发生歧义,Ruby会报语法错误
# 比如下面的arg3不知道应该穿给method_one还是method_two
method_one arg1,method_two arg2,arg3

# 可以传hash参数
def my_method(arg)
    puts arg
end
# 调用时不用{}
my_method('key'=>'value')  # {"key"=>"value"}
my_method(key:'value')     # {:key=>"value"}

# 可以使用*号,代表可变参数
def rest_method(arg1, *args)
    puts "first argument is #{arg1}"
    puts "other arguments is #{args}"
end
rest_method(1,2,3,4)
# first argument is 1
# other arguments is [2, 3, 4]
rest_method(1, key1:2, key2:3)  # hash参数必须放在最后
rest_method(1, key1:2, key2:3 ,4) # 否则会报语法错误

# 方法支持默认参数
def default_arg_method(a, b, c = 3, d = 4)
  p [a, b, c, d]
end
default_arg_method(1, 2)        # [1,2,3,4]
default_arg_method(1, 2, 5)     # [1,2,5,4]

17、 代码块作为参数是Ruby的一大亮点,也是初学者最看不懂的(说的是自己)。在向方法发送消息时,代码块参数始终是最后一个。代码块可以使用do … end或{ … }

my_method do
  # ...
end

my_method {
  # ...
}
# do...end 的优先级比 {...} 低
# 通常{ ... } 用在单行

# 传入的代码块也支持参数
my_method do |arg1, arg2|
  # ...
end

def my_method
  r = yield(1,2)  # 用yield关键字可以调用代码块参数
  puts r
end

my_method {|a,b| a+b}  # print 3

# yield传参也可以不带括号
def my_method
  yield self
end

place = "world"

# 分号后声明代码块内的局部变量
my_method do |obj; place|
  place = "block" # 这里的局部变量不会修改外面的变量
  puts "hello #{obj} this is #{place}"
end

puts "place is: #{place}"  # 输出 place is: world

更多Ruby函数调用的内容可以参考官方文档

18、 Ruby的控制流语句和C系列语言不同,代码块没有花括号,看到这我觉得Python的语法还是可以的,还有Ruby给它垫底

def condition(a)
    if a == 0
      puts "a is zero"
    elsif a == 1
      puts "a is one"
    elsif a >= 2
      puts "a is greater than two"
      puts "or a equal to two"
    else
      puts "a is some other value"
    end
end

# 除了if ... elsif ... else 
# Ruby还有unless这种奇葩语法
unless true
  puts "the value is a false-value"
end
# 等价于
if not true
  puts "the value is a false-value"
end
# unless也可以与else结合使用
unless true
  puts "the value is false"
else
  puts "the value is true"
end

# if 条件可以写在末尾
a = 0
a += 1 if a.zero?
p a # print 1
# unless也一样
a = 0
a += 1 unless a.zero?
p a # print 1


# 循环语句
a = 0
while a < 10 do
  p a      # 0,1,2,3,4,5,6,7,8,9
  a += 1
end
p a        # 10

# 
a = 0
until a > 10 do
  p a     # 0,1,2,3,4,5,6,7,8,9,10
  a += 1
end
p a       # 10


# for循环
for value in [1, 2, 3] do
  puts value
end
for value in 1..3 do
  puts value
end
for (key,value) in {k1:1, k2:2, k3:3} do
  puts "#{key}=>#{value}"
end

# Java和JS使用forEach遍历时无法打断循环
# 但Ruby可以
[1, 2, 3].each do |value|
  break if value.even?
  puts value
end
# print 1

# Ruby中没有continue但有next
result = [1, 2, 3].map do |value|
  next if value.even?
  value * 2
end
p result # prints [2, nil, 6]

# next接受一个返回值
result = [1, 2, 3].map do |value|
  next value if value.even?
  value * 2
end
p result # prints [2, 2, 6]

19、 类的定义,与类的属性

class Greeter
    def initialize(name = "World")
        @name = name
    end
    def say_hi
        puts "Hi #{@name}!"
    end
    def say_bye
        puts "Bye #{@name}, come back soon."
    end
end

greeter = Greeter.new("Pat")
greeter.say_hi   # Hi Pat!
greeter.say_bye  # Bye Pat, come back soon.

greeter.name   # 报语法错误,不能直接访问实例变量

# 类允许定义多次,前面的定义会和后面的定义merge
class Greeter
    attr_accessor :name  # 指明name属性可以访问,相当于给name属性加了Getter/Setter
end
greeter.name
greeter.name = 'Andy'
greeter.say_hi   # Hi Andy

# 通常我们要限制属性的写操作
class Person
    def initialize(name="holmofy",age=0)
        @name = name
        @age = age
    end
    # getter
    def age
        @age
    end
    # setter
    def age=(age)
        if age<@age
            puts "Age can only grow"
        else
            puts "#{@name} grow up to #{age} years old"
            @age=age
        end
    end
end

20、 类的继承和大多数语言一样

class A
  Z = 1
  def z
    Z
  end
end

class B < A
end
# 和大多数语言一样,子类允许调用父类的方法
p B.new.z #=> 1

class A
  def m
    1
  end
end

class B < A
  # 子类可以重写父类的方法
  def m
    2
  end
end

class B < A
  def m
    2 + super  # 如果想要调用父类的同名方法可以使用super关键字
  end
end

p B.new.m #=> 3

最后吐槽一下:排名低就是有排名低的理由,和排名前十的语言相比感觉还是差了点。

你可能感兴趣的:(其他)