Ruby 谈谈我对 Ruby 的看法

jakit · 发布于 2017年12月28日 · 最后由 zzz6519003 回复于 2018年01月01日 · 1421 次阅读
96

引言

我上司给我发一篇 java sonar 啥的文章,他觉得很神奇,我就告诉我上司,2013 年左右 王垠 写了 RubySonar 和 PySonar,我还给上司发了 Rubocop。

他的第一反应是:卧槽这什么鬼玩意(一脸排斥),我跟他说,我的 amber-kit 是借助它辅助编码的。

上司对 Ruby 真的比较反感,他说武汉根本招不到人,是根本上的,他跟我说他朋友的公司前几年就是用 Ruby,后来全用别的语言重构了。

他有一次问我:

为什么你不是用 Go / C++ 来写 amber-kit,先不讨论别的,关键是你这东西没办法招人啊。

由于公司一直用 PHP,其实我对 PHP 真的很反感,不是一般的反感,简直是逆天弑神的反感到炸。

Ruby 是否为一个好工具

我认为是的,但只是我自己

Ruby 的好处之复用思想

不需要考虑太多复杂的 Interface、Generic、template 等复杂的东西,用 mixin 就可以解决。

Interface、Generic、template、mixin

实现的目的,不过就是暴露一个接口,让类与类之间更好的契合在一起,好比古代木匠的卯和榫。

Interface

接口的方式是传统面向对象思想的接口(interface)与实现(implement)方式对接两个对象。

template

模板的方式是通过预编译宏一样的处理,就是模板类 typename T、class T 只要做了 T::method 或者 T t.method 的调用,在模板委托目标类一定要实现。

template <typename T>
void method(T t) {
    t.method();
}

class MyClass {
    void method() {}
}

MyClass mc;
method(mc); // 调用 mc.method

其实这是一种典型的【代理委托】设计模式,编译器会在【编译时】检查 mc 的类,也就是 MyClass,是否实现了 method 方法,如果你放个 int a 到 method(a) 来调用,会报错 int 没有方法 method。

template 一般是 C++ 这种没有完整元信息的实现语言以预编译形式处理的,也就是说,跟宏接近,模板类不是类型,而是模板

Generic

泛型的方式和模板相近,比模板强,区别在于,泛型跟普通类一样,也是类型。

在 Java 编码的时候,你不能 new T,但是你能反射出当前运行时 T 的类再去 new。

mixin

说了这么多废话,我要进入正题,开始卖萌了

module Comparable
  def >(param)
    self.<=>(param) == 1
  end
  ...
end

class Size
  include Comparable
  attr_accesor :size

  def <=>(param)
    self.size - param.size
  end
end

比起接口、模板、泛型,Ruby 可以更简单、直接的委托一个方法到模块,然后复用。

mixin 是动态类型、动态注入的

由于是动态注入的,所以随处都能 include,还可以灵活的 extend,虽然 extend 出来的 single_class_method 的方式有点让人联想 JavaScript 的 bad practice 的 prototype clone style programming。但是 Ruby 并不建议你去主张搞那么多 SCM,而是提供这项功能,发挥其作用。这是真善美的!

动态类型可以解决开发者不用担心编译时太多类型不匹配导致开发者心烦,你试着拿着玉米一粒粒抠掉就能明白你去一个个解决类型匹配问题其中的道理。

Java 抽象做大了,类型不匹配的情况很多,而且超级恶心,很棘手。

Ruby 是强类型的,找不到方法还是会抛出错误,虽然动态会带来错误的可能,但还是比较严谨的。开发者可以先解决问题,后修复 bug,这是自顶向下模型。如果你有强迫症(也不是什么强迫症了啦,只想要严格一点),辣么,每个对象都有 is_a?,提供给你筛选符合的类型并处理不符合的类型。

Ruby 的好处之强类型动态

Python 被很多人公认是很好的入手第一门语言,因为它在强类型动态的特点,让初学者不需要那么老成持重地声明类型、规范类型,但是懂得遵守类型。

这让开发者不需要去必须把规范执行得很死板,但是又能遵守约定类型来开发,不至于像某些情况说“完全不顾类型的直接编写代码”。

我要开挂一样的喷 PHP,谁都别拦我

PHP 是世界上最“土得掉渣,还告诉爸妈,回家种田吧” 级别的语言。

喷点1. 几乎都是全局方法

PHP 延续了 Perl 的任务式开发思想,把很多东西都不包到模块里面,直接全局方法调用。

喷点2. 复合类型

PHP 世界的粒度是以 Scalar 作为基本单位的,每一个 PHP 变量(Scalar)都既是 int 也是 double 也是 string 也是 object,这跟 Perl 5 一毛一样(说人话)。

大家可以用 Devel::Peek 看一下 Perl 每个 Scalar 内存结构就知道了。。。

use Devel::Peek;
        $a = 42; $a = "hello";
        Dump $a;

# 输出
SV = PVIV(0xbc288) at 0xbe9a8
          REFCNT = 1
          FLAGS = (POK,pPOK)
          IV = 42
          PV = 0xb2048 "hello"\0
          CUR = 5
          LEN = 8

我来跟大家介绍一下,IV 实际上是 IntValue,PV 就是 PointerValue,其实还有很多,像 @array 还会有 RV、ARRAY FILL MAX 等。具体自己看 perldoc

以下引出 PHP 类型结构,跟 Perl 没啥区别

struct _zval_struct {
    /* Variable information */
    zvalue_value value;        /* value */
    unsigned char type;        /* active type */
    unsigned char is_ref;
    short refcount;
};

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

Perl PHP 当你第一次赋一个 int 的时候,它在结构体的 int 部分指针 = 被赋值的 int,以此类推。。。

这种复合类型好处是方便不了解编程的人能够方便在 a = 1; a .= "str"; 的时候隐式处理,不需要你 to_s toString() atoi()。

然而,这样已经从设计的最开始就已经违背了类型系统的初衷,应该说,这样的语言的思想是朝着【无类型】设计的,所有的变量、标量都能连在一起运算运行,无需开发者关心。

喷点3. 没有包管理(不提 composer 这种名不正言不顺的东西)

PHP 天生几乎都是设计成 SAPI,用 C 扩展的,类似于 JSP 之于 Java 的方式,本来就是作为直接网页嵌入编写式语言,跟包管理更不太融洽,如果你来设计维护 JSP,会辣么无聊让本来作为模板语言嵌入的 DSL 增加包管理吗?显然复杂化了。

所以 PHP 天生包管理就不强,include 的东西基本上都不是包,而是文件,然后命名空间真的有点脑残,\Namespace 这种前面要带个斜杠才能用全局的 Namespace,否则的话,你前面设定了自己的 NamespaceA 之后继续写代码会引用的是 NamespaceA\Package。 然后我有个 Laravel fans 说就把 \Namespace 看成是目录,我呵呵哒,你见过 java 会 .java.package 写吗?先天残疾不要把这种问题丢给开发者。

就算有了 namespace,不等于 PHP 就有了包管理,还是有很长的路走的。

喷点3. 杂七杂八的设计

PHP 几乎没有 universal standard coding style,而且每个框架几乎都是独立维护的。像 snews 这种程序,是一个老外写的新闻 cms,一个打包好的 php 脚本就是程序全部了,各种奇葩的东西都有,不过早期也是这么设计的。

喷点4. 天生是为 CGI 设计的

写过 PHP 扩展的人都知道,有 PHP_MINIT_FUNCTION PHP_RINIT_FUNCTION,尤其 PHP_RINIT_FUNCTION 这个东西,是当一个 REQUEST 请求初始化时运行的,说白了就是 CGI 拿到环境之后开始运行调用的。

这根本就不是一门独立的编程语言,虽然它照样可以使用来进行 low level programming,但是 socket 编程会很有问题,队列操作的时候,如果一个 socket 已经关闭了,那么接下来调用查询状态会出现 FALSE 或者 NULL,但是无论哪种都拿不定,而且 PHP 根本没法判断这个 FD 是个 FD,因为 PHP 对于这种类型根本不是类型,一个比较底层的引用的聚合类型你叫我怎么判断。(这里理解有点难度,如果能读懂我的意思就更好了)

所以才会出现 Swoole 和 Yaf,但是这样的话,PHP 受制约性也太大了,比 Lua 还差,Lua 好歹可以包装对象,虽然不是完整的面向对象,Lua 好歹也有 meta-table 元表,跟 JavaScript prototype 类似的实现。

总之 PHP 就是垃圾中的战斗机,根本不足挂齿,说自己是做 PHP 的都觉得脸红耳赤羞得没法下台了(没事,找个地方羞去,没人看的)……(此处省略一亿字)

Ruby 其它的好处

运算符重载

这将允许你代码上能快速编码,不需要那么多 number.add().div(),更加灵活

单例类

这将允许你进行对象的后天编程,让喜欢抽象的程序员留了一大片自由幻想的天空

极易扩展

无论自己写模块,包装 gem,跟 C 交互,一切都是干净的、卫生的、便利的,因为它的思维粒度是基于 struct class 和 struct object。C++ 的开发者们把它当作 Lua 使用作为你的辅助工具也无可厚非。JRuby 和 Java 也有很棒的交互能力。

约定

约定是 Ruby 最核心的东西,它体现在 Rubyist 手写的每一句表达式、每一个控制语句,以及完成的每一种设计上,它不仅仅让你的编码写作变得相互认可(不强求绝对统一),模块、对象之间交互也变得宽松简约。

另外,这些还是需要一个开发者有良好的编码素养才能达成的,但是也并不难。

看着 Ruby 的开发者越来越受两边排挤,我也看不下去了,希望更多人支持它!

知乎链接:https://zhuanlan.zhihu.com/p/32428688

共收到 32 条回复
27359

胆子好大啊,竟敢喷php,不怕引战吗。

虽然你说的都对

10430

说实话,你这样会显的素质比较低。

大家一般会觉得做ruby的人本来数量就不多,素质还不咋地。

96
module Comparable
  def >(param)
    if self.<=>(param) > 1
      return true
    else
      return false
    end
  end
  ...
end

这个代码……

96

谢谢纠正,改了一下代码

E8ebf1

应该发去 PHP 社区😂

96
10430luoyou 回复

抱歉,原谅我这位有个性的开发者。👃

1107

我又来了,一条条来吧

引言

咱们单纯说技术,说实话,我觉得贵司技术管理的理念有点...过于过时

我上司给我发一篇 java sonar 啥的文章,他觉得很神奇,我就告诉我上司,2013 年左右 王垠 写了 RubySonar 和 PySonar,我还给上司发了 Rubocop。

他的第一反应是:卧槽这什么鬼玩意(一脸排斥),我跟他说,我的 amber-kit 是借助它辅助编码的。

RubySonar 和 PySonar 不谈,来说说 Rubocop ,它是 Lint 工具,我不知道你还有你的领导有没有意识到 Lint 这一点,但是,Lint 工具在软件工程上(不仅仅是某些语言)有非常进步的意义,可以说是当下的必备工具了。

即使 Java 领域,具体的实践推荐参阅 使用 Lint 改进您的代码 | Android Studio

Interface、Generic、template、mixin

首先,这一节立意就是错的,Ruby 的面向对象基于 Smalltalk 的消息模型,这个模型下,对象的作用是处理传入的消息,(纯理论层面)接口、泛型、甚至类型都不是必要的。

其次,你的代码。。。include Comparable 之后,只需要实现 <=>(other) 方法,你的代码只需要写成

class Size
  include Comparable
  attr_accesor :size

  def <=>(param)
    sefl.size - param.size
  end
end

开完会继续

96
1107jasl 回复

我知道,我在公司写 PHP 就在用 SublimeLinter-php,php -l 就可以了,要讨论 ruby 的 linter 其实 Visual studio code 还有一个 Reek,自带的 -wc 就简单好了。

看姜叔叔又开始跟我挑刺了,好怕怕

1107
32jakit 回复

你生成代码也没少多少,但是你的结论来自于你写错了,那么好,我给你正确的代码,你告诉我这叫代码没少多少?

96
1107jasl 回复

嗯嗯嗯,谢谢姜叔叔👏 ,嗯嗯,我正乖巧地坐着听呢~

1107
32jakit 回复
module Comparable
  def >(param)
    if self.<=>(param) == 1
      return true
    end
    false
  end
  ...
end

你改出来的代码就这德行? self.<=>(param) == 1 的结果已经是 Boolean 了,你还 return true 什么?

96
1107jasl 回复

反应过来了。。。

1107

你改了 >,那这部分呢?

def <=>(param)
    if self.size > param.size
      return 1
    elsif self.size == param.size
      return 0
    end
     -1
  end

看不出来要不要我教你怎么改成一行?

96
1107jasl 回复

已经更新了,突然反应过来,缓存吧。

不过谢谢指正

1107
32jakit 回复
def <=>(param)
    self.size <=> param.size
end

那么,这么改之后,跟你原始代码比,简化了多少?

43585f

直接

self.size <=> param.size

96
1107jasl 回复

嗯,我知道了,我把那部分结论的去掉了。

1107
32jakit 回复

我不是挑你刺,但我希望你先写好代码,再出来扯有的没的,何况你扯了大段虚的概念(我在这就不评论你说的对不对),懂的人用不到你教,不懂的人你说的那堆也没法让人懂

96
1107jasl 回复

嗯,好的,谢谢,我会用心的

1107
32jakit 回复

另外,你拿 Comparable 做例子,可文档里都写的很清楚了,你先看看文档就不会有这么多问题

96
1107jasl 回复

我知道文档里面有,疏忽了~ 不过文档里面的是 C 实现的 Comparable

1107
32jakit 回复

跟 C 有什么关系? https://ruby-doc.org/core-2.4.0/Comparable.html 自己看

class SizeMatters
  include Comparable
  attr :str
  def <=>(other)
    str.size <=> other.str.size
  end
  def initialize(str)
    @str = str
  end
  def inspect
    @str
  end
end

开头这不就是你的例子么

96
1107jasl 回复

我说的是 module Comparable

1107
32jakit 回复

我发的不是 module Comparable 那是什么?

96

我说的是 module Comparable 本身的实现,比如:

obj < other → true or false click to toggle source
Compares two objects based on the receiver’s <=> method, returning true if it returns -1.


static VALUE
cmp_lt(VALUE x, VALUE y)
{
    VALUE c = rb_funcall(x, cmp, 1, y);

    if (rb_cmpint(c, x, y) < 0) return Qtrue;
    return Qfalse;
}
1107
32jakit 回复

需要关心本身实现么?

文档开篇说的什么?

The Comparable mixin is used by classes whose objects may be ordered. The class must define the <=> operator, which compares the receiver against another object, returning -1, 0, or +1 depending on whether the receiver is less than, equal to, or greater than the other object. If the other object is not comparable then the <=> operator should return nil. Comparable uses <=> to implement the conventional comparison operators (<, <=, ==, >=, and >) and the method between?.

讲得不够清楚?

96

嗯,好啦好啦,每次都被叔叔搞得很尴尬,吾辈知错了

15139

包管理那段也是槽点。Ruby在gems之前不也没有包管理,装个rails都要vendor下面拷一大堆文件,运气好是plugins,运气不好直接裸拷lib文件……

6553
32jakit 回复

是军爷

6553

不过你们就内容一问一对我们可以涨姿势

96
6553zzz6519003 回复

嗯,我只是抱着【谈谈看法】的态度说出来,分享一下我自己的看法,至于对与不对,我是无辜的。

就像医学,远古的时候的人类神农,发明些草药,可能只是“起到某种作用”,但是实际上哪种成分,当时的人类可能是不清楚的,随着尝试药物的人多了,才把药物起的作用分清楚。

小白鼠,就是科学道路上的牺牲者,没有所谓的天才神啊啥的,都是试出来的。

对于我在文中对 接口的思想,也是我自己的个人总结笔记,如果大家看了觉得有帮助,那就更好,如果不以为然,有更好的想法,也 Okay。

如果真的有一位语言学家出现在这里,能说出 xxxx 为什么会这样,因为 xxxx,解谜,这样更好了。

但是如果只是吹毛求疵,说我是追求装逼什么的,那我下次知道些什么,我宁可不分享,我留着自己用不好?

6553
32jakit 回复

么么哒~

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册