• 非常的不想要 prefix attribute name

    Rails 6 可以 override model attribute 级别的 error format https://blog.bigbinary.com/2019/04/22/rails-6-allows-to-override-the-activemodel-errors-full_message-format-at-the-model-level-and-at-the-attribute-level.html

  • 我们不说 “平庸”,“低水平”,我们说平均水平

    软件工程目的真的就是为了实现低水平的程序员都能完成工作?

    软件工程的目的是利用现有资源完成目标。精英工程师是稀缺资源,绝大部分公司不可能全员精英,如何利用好平均水平工程师才是关键。

    还是优秀人才难招聘(或者不愿招聘)、难培养(或者没能力培养)的结果?

    同上点,优秀人才稀缺是不可改变的现实,请不要把人才培养的责任推给企业,企业顶多占 20%,个人因素最重要。

    平庸 平均水平员工就能以可接受质量完成工作” 是我们应该追求的目标?

    是。

  • 公司隐含的要求是:员工要能解决问题,并且还要平庸,所以让员工变得平庸的技术反而能大行其道。

    是不是有点因果倒置,平庸员工都能解决问题的技术就应该大行其道。别人都上先进生产线,招高中毕业生,培训 2 天直接上岗,你还非要找 20 年经验老手工师傅。当然不是说老手工师傅不行,只是适合的场景不一样。

    有不少公司招新人进来不让接触根本的东西,自己封装了一层 “指导原则” 的东西,招进来的人在这个框架上工作,甚至连编程语言(Ruby)本身、技术框架(Rails)本身都没怎么掌握,就被赶鸭子上架,照葫芦画瓢的 copy and paste 写代码。

    copy paste 写代码跟公司没什么关系,没有公司会禁止员工进步。就软件行业来说,大批优秀开源项目、文章等学习资料唾手可得,进步不了怪公司有点说不过去。

    总结:平庸员工就能以可接受质量完成工作是生产方式优秀的体现,有上进心的人可以没有障碍(科学上网不算)的接触业界先进知识是行业优秀的体现。

  • 这个方法怎么定义 at 2020年06月29日

    你需要的是子方法跳出到父方法的能力,考虑一下用catchthrow

    def fun
      r = catch(:tag) do
        a_fun
        b_fun
        ...
    
        return 0
      end
    
      return r
    end
    
  • 只有我名字没打码,明显是冲我来的

  • Raft 笔记 at 2020年06月26日

    Raft 是一个复制 log 的算法

    Raft 是解决分布式系统一致性的算法,log 其实是 commit log,不说明一下容易产生歧义。

  • Twitter 上被 Hey 刷屏了 at 2020年06月23日

  • 哪个资本闲着没事还去宣传技术?

    Rails 本身不适合业务急速扩张的场景,倒不是 Ruby 本身的 scalability 问题,是团队的 scalability 问题。急速扩张大量需求的是蓝领工程师,Rails 作为蓝领工具没有特别优势,甚至还有不少劣势。

    归根结底还是因为软件相关行业利润太高,能堆人解决的问题都不是问题;rails 工程师大多是 generalist,堆人的公司也用不上。

  • JSON.generate(hash, space: " ", indent: " ").sub(/\s/, '')

  • require "socket"
    require "nio"
    
    def run(host, port)
      server = TCPServer.new(host, port)
      puts "Listening to #{host}:#{port}"
    
      selector = NIO::Selector.new
      monitor = selector.register(server, :r)
      monitor.value = ->(_) do
        socket = server.accept
        puts "New client #{socket}"
        client_monitor = selector.register(socket, :r)
        client_monitor.value = ->(mon) do
          s = mon.io
          puts "Got: #{s.read_nonblock(4096)}"
        end
      end
    
      loop do
        selector.select { |monitor| monitor.value.call(monitor) }
      end
    end
    
    run('localhost', 1234)
    
  • Puma 是多个地方用到IO.select,reactor 从IO.select替换到NIO::Selector#select https://github.com/puma/puma/commit/e83a4954e4c0214d18beb594ddf598fafdf058d7#diff-8b7db4d5cc4b8f6dc8feb7030baa2478

    IO.select 跟 NIO::Selector 的区别官方 wiki 就有 https://tonyarcieri.com/a-gentle-introduction-to-nio4r

  • 是 Thread B 里的selector.select do |monitor| block Thread A 里的monitor = selector.register(socket, :r)

    • NIO:: Selector#selectIO.select的替代品,不应该同时用.
    • TCPServer 先 register 到 nio selector, 然后再调用NIO:: Selector#select。server monitor 的 callback 里接收客户端 io 对象,然后把 io 对象再注册到 nio selector 里。
  • 推荐算法老师 labuladong at 2020年05月06日

    我那句提反转二叉树了?贴个老梗显得自己懂得多?一个极端特例当论据是不是抬扛?一直用反问句回复别人是不是自我感觉很良好?

  • 推荐算法老师 labuladong at 2020年05月05日

    工程师也会 “相轻” 的,跟不同背景的同事吹逼,聊到算法,设计模式以及 “众所周知” 的 pattern 一定不能露怯,否则将会被暗暗鄙视(其实大部分人都是半桶水,所以都会很默契的不会聊的很深入)

    面试中算法有点像义务教育里那些感觉没用的课程,一是能快速筛掉学习能力差的人,二是算法确实对编码有影响。

  • 三楼的需要在方法外定义 param

    不需要,写里面也一样。

  • 还不是很理解,为啥 broadcast 的时候要用 thread pool,因为 io 已经 异步了

    ActionCable 的实现是在内存里维护一个订阅列表的 hash,key 是 channel,value 是一个订阅者回调 lambda 的 array,

    回调 lambda : https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/channel/streams.rb#L146-L154

    def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
      handler = stream_handler(broadcasting, user_handler, coder: coder)
    
      -> message do
        connection.worker_pool.async_invoke handler, :call, message, connection: connection
      end
    end
    

    broadcast 的时候: https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/subscription_adapter/subscriber_map.rb#L36-L45

    def broadcast(channel, message)
      list = @sync.synchronize do
        return if !@subscribers.key?(channel)
        @subscribers[channel].dup
      end
    
      list.each do |subscriber|
        invoke_callback(subscriber, message)
      end
    end
    

    invoke_callback最终是上面那段 lambda

    -> message do
      connection.worker_pool.async_invoke handler, :call, message, connection: connection
    end
    

    worker_pool是一个 thread pool (Concurrent::ThreadPoolExecutor),async_invoke是往 thread pool 里塞任务,任务异步执行,所以遍历 subscribers 时是不会阻塞的,而消息何时向全部 client 发完取决于 thread pool 消化任务是的速度了。而且整个 ActionCable 是公用一个 thread pool 的。

  • 可以看一下 https://hashrocket.com/blog/posts/websocket-shootout 粗略比较了各种编程语言/框架 websocket 的指标。

    结论

    When it comes to websockets, Clojure, Elixir, and Go are all choices that balance great performance with ease of development. NodeJS is in a lower performance tier, but is still good enough for many uses. Rails can only be recommended when part of an existing Rails application with few concurrent clients. C++ offers the best performance, but it is hard to recommend due to the complexity and difficulty of development.

    需要指出的是做测试的时候 Rails 6 刚出,puma 还没有用上 nio4r,所以 Rails 支持的连接数看起来弱爆了,但是不代表现在 Rails 的真实水平。

    ActionCable 比较大的问题是 broadcast 是基于线程池的,线程池大小默认是 5(可以通过 config.action_cable.worker_pool_size 调整),换句话说一个 puma worker 同一时间只能同时向 5 个 websocket client 发消息 😂

  • 装饰器可以对原方法进行修饰,返回一个修改之后的方法,在打印日志,切面编程中运用广泛,你说 ruby 开放那你可以试一下在不改变函数名的情况下对原函数进行修改

    module LogDecortor
      def log_method *method_names
        mod = Module.new do
          method_names.each do |method_name|
            define_method(method_name) do |*args, &blk|
              puts "Method #{method_name}: start"
              ret = super(*args, &blk)
              puts "Method #{method_name}: end"
              ret
            end
          end
        end
        self.prepend(mod)
      end
    end
    
    class Foo
      extend LogDecortor
    
      log_method :bar
      def bar
        return 1
      end
    end
    
    p Foo.new.bar
    

    另外 Ruby 即使有不改变方法代码本身就改变方法行为的能力,但是实践中还是比较克制,像加日志、打点一类的需求有另外的方法,就不展开说了。

    第二 javascript 只是我用来举例,可能你没懂我在说什么

    只是没懂你的举的例子有什么太大的好处。

    第三纯函数式语言是写不出复杂的系统的,或者说是不容易写出复杂的系统

    纯函数式编程不容易写出复杂系统是因为程序员要求高一点。

    所以这里的函数式编程就是一个概念,允许方法有副作用

    "函数式编程就是一个概念"不是我的原话吗?我本意是现在市面上绝大部分面向对象语言都可以写 “函数式编程” 的代码,只是前端生态很推崇,别的领域还没有一要使用 “函数式编程” 的场景。

    Ruby 2.7 曾经有过管道符 https://bugs.ruby-lang.org/issues/15799 ,你对函数式编程那么了解肯定知道管道符对函数式编程的意义,虽然种种原因取消了,但是可以反映出 Ruby 是拥抱函数式编程的,你可以期待一下 ❤

  • 比如增加 python 的 AOP 特性 (装饰器)

    Ruby 足够开放,不需要语言层面装饰器。

    def 定义的方法名也是一个变量,可以覆盖(就是重复定义)类似 javascript,

    javascript 还没有好到需要别的语言模仿吧 😅 覆盖方法不就是 override 么……

    然后支持函数式编程,高阶函数(支持传入函数,传出函数),现在来看还不能算真正支持函数式。

    高阶函数核心在与 “穿作用域”,而在 “穿作用域” 领域,Ruby 比大多数语言强很多。函数式编程也仅仅是个概念:要不你就用纯函数式编程语言,写不出违反函数式编程 pattern 的代码;要不你就用其他语言假装在函数式编程:有些领域大家都在假装,有些领域大家还没开始装,并不是说不能装,只是还没遇到需要装的场景。

    学新语言还是要有空杯的心态。

  • Rails 原生的 websocket 比较弱,这种全靠 websocket 的框架还是观望一下比较好(实在忍不住可以用 phoenix live view)

    看过Samuel Williams的 Ruby 处理百万 websocket 连接The Journey to One Million by Samuel Williams后一度对 Ruby 在稍微高一点的并发场景产生了幻想。

    Samuel Williams 主推的是基于FiberAsync做 ruby 的 websocket server,然而 Rails 团队似乎并没有打算用 Aysnc https://github.com/rails/rails/issues/35657

    关于 websocket 另一条路是用 Anycable,用 golang 或者 erlang 做 websocket server,再通过一另一个 gRPC 服务跟 Rails 应用代码交互。Stimulusrelex 文档上说已经支持 Anycable 了,但是用 Anycable 增加了部署的复杂度,会不会违背了 Stimulusrelex 的初心 😅

    With StimulusReflex, you'll ship projects faster, with smaller teams and re-discover the joy of programming.

    另外分享几个事情:

    • 好多年前就有一个基于 websocket 渲染的 Ruby web 框架 https://pakyow.com/ ,开发者也不用自己写 js 代码,跟 Rails + Stimulusrelex 体验很像,停更了几年后最近半年突然 release 1.0,用上了 Async,我猜可能是 Async 解决了他们 websocket scale 的问题。
    • Anycable 的作者做的benchmark表明, async 并没有比 puma + actioncable 好太多 😢
  • xxx.call(10)可以写成xxx.(10)或者xxx[10],都是语法糖,用多了讨人厌

  • param = []
    define_method(:method1) do
      define_method(:method2) do |x|
          param.push(x)
      end
      return method(:method2)
    end
    
    f = method1
    f.(10)
    
    p param
    
    
  • 微服务管理者能分山头,技术人员能折腾着玩,除了对产品不一定好,其他都照顾到了。

  • 一文看懂 IO 多路复用 at 2020年03月23日

    一般开发者不需要操心这个,对 rails 开发者来说,这部分细节是在 puma 等 webserver 这边。puma 之前版本是用的IO.select操作 socket,绝大部分场景够用了。后来有了 actioncable 才用 nio4r(文中的 epoll),主要是突破IO.select最多只能接 1024 个 socket 对象的限制 https://github.com/puma/puma/commit/e83a4954e4c0214d18beb594ddf598fafdf058d7#diff-8b7db4d5cc4b8f6dc8feb7030baa2478

  • 没有必要把 B 放到 A 里面,你的问题是 A 以及子类依赖另外一个类,你需要把这个类传给 A 或者 A 的子类。把 B 放进 A 不解决问题。

    require 'active_support/core_ext'
    
    class B
      def b
          puts "A::B#b"
      end
    end
    
    class A
      class_attribute :b_like_class, default: B
    
      def a
        b_like_class.new.b
      end
    end
    
    class B2 < B
      def b
        puts "B2.b"
        super()
      end
    end
    
    class A_CHILD < A
      self.b_like_class = B2
    end
    
    A.new.a
    A_CHILD.new.a