• 基本上是 Rails 全栈思路的延伸:

    1. 模板靠后端渲染,erb 加各种 helper,最大限度的利用 Rails 的 view 层
    2. 数据放在 data attribute 里
    3. Stimulus 提供基本的组织架构,也即是 controller,并托管它们的生命周期

    总而言之,它为页面的 JavaScript 代码提供了一个统一且规范的组织,但做点正经事情还是的得配合 Rails 已有的各种方便功能(还有理念)。如果你的代码组织问题是 Rails 的 SJR 带来的 JavaScript 代码碎片化,它也帮不上什么忙。

  • Linux、Ruby 不冷没天理! at 2018年01月30日

    我差点以为自己进的起点…… LZ 快设计一门语言把 Ruby 摧枯拉朽的干掉

  • Rails 的一个 controller action 为整个页面提供数据,因此很多页面都不止一个实例变量。

    比如:

    1. 一个 index 页面,有几个筛选的下拉框
    2. 一个 show 页面,也有几个下拉框,比如关联属性的取值范围

    这些都是很正常的需求。你当然可以把主要资源之外的数据全放在一起,比如 hash,但这只是为了强行满足所谓的规范,没多大实际意义。为多个资源合理的命名更有利于维护。

  • Elixir Style Guide at 2018年01月11日

    👍 这个好像是最接近官方风格的那个 style guide,翻译还是很有价值的。准备等 1.6 出了正式版就升级过去的。

  • @Jao 这算是最容易维护,以后也最少改动的方式。控制一下异常捕获的类型和范围就行。

  • 这其实是两个问题:

    1. 前端如何去查询进度
    2. 后端如何更新进度

    前端基本就是 web 接口的事情,可以用 REST API 定期轮询也可以用 Websocket 来推送。两者代码量其实差不多。

    后端需要的是持久化一个 job 的 id, state, progress,至于是放在 Redis 里还是 RDBMS 也都无所谓。只要实际干活的后台任务定期更新 job 就好。我现在更倾向于 RDBMS,因为 job 这种数据往往以后还需要查询到,也有可能附加其他信息(导入时间/完成时间,关联的资源等)

    之所以这么说,是因为后端的 progress 如何更新跟前端如何获取数据没有关系。比如后端通过 background job 在导入完成后更新 job progress,但给前端的接口仍然是 REST API。或者后端用 ActionCable 封装一个 Websocket 接口,但在 channel 内仍然是轮询获取最新进度。

  • 无聊翻了下书,貌似出现 Song object 的很少,第一次出现的是这个地方:

    虽然看不清楚代码和普通文字是否有字体差异,但我觉得这个地方用 Song object 也算合理和容易理解。因为代码里也只有一个类,Song object 算是个泛指。

  • @jasl Medium 和简书都不支持代码高亮吧。还得贴 gist 链接。除此之外都不错。

  • Emacs 闲谈 (二) 自如的分屏 at 2017年11月21日

    主力用 Neovim + VimR,最近二次入 Spacemacs 的坑。Spacemacs 的快捷键助记性非常不错,实际上这是它的核心理念之一:Mnemonic,Discoverable,Consistent,Crowd-Configured。所有快捷键都归类分组,比如 SPC b 是 buffer 相关,SPC w 是 window 相关,下层键位都有视觉上的提示的 (which-key) 。

  • Ruby 爬虫框架 at 2017年11月06日

    @tony612 我也想到这个了。Elixir 的并发模型应该是很适合爬虫的场景。

  • 你这只能看出 content download 时间很长,其他的信息都是无意义的,别人没法帮你。你得一步步从外到内追踪一下,看 Nginx log 和 Rails log 哪里慢,缩小范围定位问题。

  • 有一定影响,不过问题大不大,还是取决于人。

    你是否会因为一个软件有一些高层抽象而看不清楚全貌?这取决于软件的复杂程度,也取决于开发者的设计水平和抽象能力。好项目会先给告诉你有哪些大块,等了解深入后再去探索每一块的内部分层。你也许无法看清楚软件的每一块细节,但肯定能看清楚大体由哪些组件组成,以及它们是如何协同运转的。

    前端工程化是同样的道理,想了解每一块的 html 是怎么样的也许把项目跑起来看比追述源码更方便。但要了解一个页面由哪些组件组成其实反而更简单。这时候 50 行的高度组件化的代码比 500 行的 html 模板更容易看懂。

    当然,开发者的抽象能力得基本靠谱。坏的抽象不如没有抽象。

  • 不管喜不喜欢这种方式,建议先尝试一下 React。对后端分层的理解套用到前端并不是完全适用的。另外,严格来说,在 React 里写的不算 html,而是用 xml 的方式描述组件嵌套关系,并且最终会编译成 javascript。

    对后端而言,view 层的最终任务都是渲染出一段 html,然后就没事了,所以相对简单一些。但对前端而言,渲染出 html 只是开始,后面还会继续处理交互逻辑,更新内部状态,重新渲染 UI。所以前端的 view 层不仅要处理展现,还要响应一些行为(并不仅仅是底层事件),这两者密不可分,很多时候需要一起修改。但模板和 javascript 分开的项目需要分两个文件,如果是按类型分的项目结构,两个文件还安排在不同的目录下,比如 app/templatesapp/views ,再加上负责纯展现的 css 甚至可能在另一个地方,这在实际开发的时候并不方便。

    这也是前端推崇组件化的原因之一,把本来就该在一起的东西放一起。React 给出的解决方法是 JSX,抛开成见看还是很方便的。另外可以带来一些额外的好处,比如用 javascript 作为一门完整的语言比受限的模板语法可以做的更多。所有对 javascript 代码组织的技巧都可以用来描述 view 层。这对喜欢这种模式的开发者来说是极大的方便,不过也提高了对人的要求。

  • 如果是备份和还原,pg_dump/pg_restore 够用了。pgAdmin 实在太丑……

  • 心疼自己,Ruby 程序员-1 at 2017年10月26日

    Ruby-1, C+++1。这貌似加得有点多

  • 如何从 MongoDB 迁移到 MySQL at 2017年10月26日

    @Draveness 对 MySQL 不是很熟,不知道现在有没有原生 uuid 的支持(之前我记得是没有)。相比之下 Postgres 的 uuid 是 binary 的,也支持自动生成。性能没测试过,不过作为官方支持的特性想来也不会很差。

  • 如何从 MongoDB 迁移到 MySQL at 2017年10月25日

    我觉得改 uuid 如果只为了审美有点得不偿失。之前改过一次,真是特别的蛋疼,所有关联都要重新建立,一不小心就非常容易错。

  • @tenderlove: Time to learn Elixir! at 2017年10月18日

    用了都大半年了

  • Ruby、函数式和 RESTful at 2017年09月30日

    没太懂 API 幂等的含义,POST 不幂等是因为创建资源是个从无到有的过程?还是因为请求参数中可以不带某些 default value?不过这篇 slide 确实不错。

  • [Elixir 基础] 复合类型: List at 2017年09月18日

    List 是单链表结构,每次添加到尾部都会完全遍历一遍。如果有这种操作(写某些算法经常出现),建议添加到 List 头部最后 Enum.reverse ,这样一次遍历效率高很多

  • 没必要。文件命名都是用小写和 dash 的格式,混几个大写自己看得也费劲。带有随机生成的 code 的文件名是唯一可能碰到坑的问题。不过本地环境又不会生成那么多……

  • 玩 Elixir 吧,语法对 Ruby 程序员来说友好很多

  • 解析邮件碰到的那些坑 at 2017年08月21日

    charlock_holmes 这名字起得好,哈哈

  • Ruby 模仿:|> at 2017年08月12日

    @mizuhashi 不太了解超算符是什么,不过看上去是挺有意思。

    管道本质上并不是把占位符拿出来(我也不太明天你的意思),而是跟 Unix pipe 一样把上一个步骤的输出传递到下一个步骤的输入。让数据转换流程的写法更方便一点。

    你的第二个例子里 call ServiceOne.new(_).do_something 应该是无效的,只能是类似 call ServiceOne, :do_something 这样。问题在于 do_something 的结果是计算好之后再传给 call ,无法做到把 some_data 或者上一个步骤的值传给 service 再运算。

  • 部分赞同。Rails 兴起时的前端还是比较简单的,甚至没有专门的前端划分(往往叫“套”页面的)。那时后端工程师兼任下前端的工程部分是理所当然的。现在前端越来越细化,已经形成一个单独的分类了。想做得好也要投入很多的时间和精力,从社会分工和专精的角度来说全栈已经不是太必要了。如果想深入发展的话,找一个主要方向专攻,适当放弃一些东西才能做的更好。

    但社会各个行业都需要一些万精油的,各方面都会一些也不代表没竞争力。最终还是看各人定位和找坑的眼光……

    而且话说回来,后端这个词比某个语言的范围更广泛得多(前端其实也一样)。

  • Ruby 模仿:|> at 2017年08月06日

    所以我才在后面接了一句,如果过程中再加一层抹开调用会如何?我暂时也想不到一个现实中的例子,但假如有两层模块调用大概是这样:

    # 一般都会写成这样比较易读和 debug
    s1 = ServiceOne.new(some_data)
    r1 = s1.do_something
    s2 = ServiceTwo.new(r1)
    r2 = s2.do_something_else
    
    # 写成一行就这样了
    ServiceTwo.new(ServiceOne.new(some_data).do_something).do_something_else
    

    这种情况下老实写过程式代码是更好的选择。但在 Elixir 中 有时候 还能用一下 pipe 并且不丧失可读性:

    some_data
    |> ServiceOne.do_something
    |> ServiceTwo.do_something_else
    

    我觉得根本上还是语言特性对职责划分的影响。OOP 更建议把职责跟某个相应的 object 绑在一起,所以方法调用会有点局限。这样的好处是因为 object 自带上下文所以通常可以少写一些代码。而 FP(或者说 Elixir,我对其他 FP 语言也不了解)因为一开始就分离了数据和函数,所以组合的时候得严格的写清楚。这是上面的例子中 Ruby 代码通常比 Elixir 代码简短得多的根本原因。

    至于打 patch 则比较矛盾。Ruby 中的对象方法有一部分是遵循职责划分,但也有不少纯粹是一层代理。就是为了写的更爽(让程序员更开心)。以 OOP 的思想来看,很难说 10.times { ... }"some date".to_time 真是按职责划分来的。不过大部分人喜欢用就是硬道理。Refinement 这个技术没推广开来也是有原因的,RubyConf 2015 上这个演讲 总结得很到位。大体上说就是到处 using 太麻烦,Rails 作为魔法界标杆都不用,还有 OOP 思想的回归为一些问题提供了另一种解决方法。本来喜欢魔法的用火球,不喜欢的觉得太危险就投奔科学去了。Refinement 告诉你为了安全扔火球之前先背一遍 xxx ……

    顺带一提,在 pipe 过程中 debug 太方便了,当然 Ruby 也可以 obj.tap(&:inspect)

    some_data
    |> step1
    |> IO.inspect
    |> step2
    
  • Ruby 模仿:|> at 2017年08月04日

    虽然我更喜欢 Elixir 的 pipe,但它强行搬到 Ruby 里来确实没什么意义,而且还是用这种特殊三角字符…… 而且这种项目其实不是在借鉴其他语言的功能,而是证明原语言的强大(看我还能做到这个哦)。类似的还有 Elixir 的 OOP ,这玩意更强大,连继承都实现了。

    从语法角度来说,pipe (|> 不是 ->) 的用途是让函数的嵌套调用变得更优美,和 OO 的链式调用不是一回事。但如果从 用类似链条的形式来表示数据转换 的角度看,两者目的是相同的。这种情况下我觉得两者各有优劣。

    比如楼主的例子写成 Elixir 应该是:

    str = "..."
    # pipe
    str |> String.split(" ") |> Enum.join("-") |> String.capitalize()
    # non pipe
    String.capitalize(Enum.join(String.split(str, " "), "-"))
    

    而 Ruby:

    str.split(" ").join("-").capitalize
    

    Ruby 的写法很容易让人心生好感,代码比 Elixir 短得多。实际上 大部分时候 Elixir 的代码都比 Ruby 要长,甚至比不用 pipe 的代码还长一点 。那 Elixir 这样做的好处是什么呢?

    一个好处是更 Explicit 。从 Elixir 代码中你能很直观的看到每个环节的数据大概是什么类型,每个函数出自哪里,从而间接的少犯些错误。Eplicit 的思路贯穿 Elixir 语言和生态,pipe 只是其中一个缩影。是否要 Eplicit 则是见仁见智。我只能说个人更喜欢这种风格,上面的例子太简单看不出来优势,但在复杂的流程中把代码表述得更严格是有好处的。

    另一个好处是 更好的职责划分和多态 。我们经常会碰到一个 object(或者一份数据)在不同的场景下需要不同的行为。这种情况下以 OO 的方式大概有以下几种做法:

    1. 把一个 object 变得功能超级多(比如 include 多个模块)。
    2. 按不同的职责建立多个 wrapper object,封装思想的一类应用。
    3. 还是建立多个功能模块,在运行时动态跟主体 object 组合。

    现实情况下 1 和 2 比较常见。但有各自的缺点。1 会让 object 方法过多,混入的模块不受控制容易方法重名导致覆盖。2 中 wrapper 和 inner object 毕竟不是一个 context(我指的 self),访问一些内部变量时会有点不方便。3 没有前两者的问题,但在 Ruby 中通常是以 Object#extend 来实现,因此有明显的性能问题。有一个模式 DCI 用的是这种技巧。引起注意不到一年后就没什么关注了,可能也跟性能原因有关。其实我觉得这个模式倒很适合 JavaScript。

    相对而言 FP 把数据和行为(函数)分离的方式能更容易的应对这个问题。一个数据可以经过不同模块的函数转换后得到一个结果,这个过程用 pipe 表达很清晰自然。

    举个例子,以下的 Elixir 代码把一段文本分词后形成一个列表,假设 Breaker 是分词的模块:

    some_text
    |> Breaker.break_words    # => [%{text: "word one", type: "noun"}, %{text: "word two", type: "verb"}]
    |> Enum.map(&Map.get(&1, :text))    # => ["word one", "word two"]
    

    如果用 Ruby 来实现,可能会这样:

    # 注入一些方法到 String 里去
    some_text.break_words.map { |word| word[:text] }
    some_text.to_breaker.break_words.map { |word| word[:text] }
    
    # 现在更推荐 OO 的写法,不过没链式调用了。
    breaker = Breaker.new(some_text)
    breaker.break_words.map { ... }
    

    第一个方法需要污染 String,第二个方法结构更好一些,其实省去 breaker 这个变量后写一行还是没问题的,但 如果流程中再加入一个模块调用呢? 这种情况下 pipe 可能还能用,但链式调用是没戏了。

    再看另一个例子,对一组数据的转换:

    (1..1_000_000)
    |> Enum.map(&do_something/1)   # 一种简写,代替 map(fn i -> do_something(i) end)
    |> Enum.reduce(&merge_something/2)
    |> OtherMod.save_to_db
    

    如果觉得数据很大,每次转换步骤都会生成一个很大的列表,可以换成 Stream

    (1..1_000_000)
    |> Stream.map(&do_something/1)
    |> Enum.reduce(&merge_something/2)
    |> OtherMod.save_to_db
    

    如果需要进一步增加效率,可以用 Flow 分多个进程去同时处理:

    (1..1_000_000)
    |> Flow.from_enumerable
    |> Flow.map(&do_something/1)
    |> Enum.reduce(&merge_something/2)
    |> OtherMod.save_to_db
    

    这个过程中参与的模块越来越多,但并没有因为模块加多而丧失表达力。而且就算把整个流程拆分到多个函数中去也能大概猜到每次转换的数据是什么。Ruby 则或多或少要受限于 self 能够响应哪些方法才能继续链式。强行链式就得往 self 里加方法来形成链条。

    最后说一句,pipe 和链式调用一样都有其限制,不是所有地方都能用 pipe 表达,这时也不必强行修改函数签名让它能够 pipe 下去。

  • macOS 只能用 Ctrl-Shift-6 ,原因见 这里

    我是用键绑定解决的 nnoremap <leader>bp <C-^>

  • erlang 与状态机 at 2017年07月20日

    这两个模块是有文档链接的,LZ 可以更新一下文章:

  • 别解决 ignore 的问题,解决同事……