居然是日文翻译过来的 +1
@nightire Ember 也有很好的地方,我个人最喜欢的是几乎无缝的升级体验,和 computed property 带来的数据驱动的思路,这两点都很重要,但也非常容易被忽略。
#32 楼 @ugoa @nightire 同问。我最先接触的是 Ember,然后是 Angular 和 React,比较之下反而发现了后两者的不少闪光点,至少对 Angular 变得不讨厌了。
我当时最不喜欢 Ember 的一点就是,作为一个高度集成的全栈框架,重要的 key feature 都要等官方的解决方案,因为这涉及到框架集成方方面面的事情。这导致 Ember 的解决方案要么就很稳定全面,要么就太 naive(注意这不代表完全不能用)。React 这种微框架的思路因为本身做的事情就非常少,也变相地刺激了社区的繁荣,鼓励第三方开发者做自己的解决方案。虽然会导致众多良莠不齐的方案,但每个领域内有口碑的也就那么几个,所以也不太会有“选择困难”的问题。至于开发者得自己写点胶水代码整合的问题,我觉得跟自己从头开发插件相比,代价还是小很多的。
@nightire 你这篇每个章节都可以扩展成一篇文章了
我虽然也不太喜欢 Rails,但这文章把所有的锅都推给 Rails 背,也觉得太冤了,其中很有一部分是因为作者的理解不全导致的概念偷换。下面逐条分析一下:
DRY 只是一种被推崇的原则,但不代表必须无时无刻的严格执行。作者把它理解成“无论什么时候 你看到一个重复的概念,你都 应该马上 去把它抽象成一个方法或类”太狭隘和绝对了。古人还说“事不过三”,生活中估计也没人能严格做到这一点。
关于代码重复和耦合的问题,作者的论据显然来自 Sandi Metz 说的“重复比错误的抽象要廉价的多”,但这其实不是 DRY 的问题,而是错误的抽象的问题。而错误的抽象往往来自于对一些概念缺乏全局理解。拿构建领域模型举例,对一个概念的完全理解往往需要反复不断的尝试和探索,这是需要花时间的,而不是了解了一点概念就 应该马上 去设计的。所以我想应该是作者对 DRY 的片面理解和执行导致了错误的抽象,从而认为 DRY 会导致代码耦合。其实这两者一点关系都没有。
这个其实是吐槽 AR 而不是吐槽 KISS 的。关于 KISS 各人有各人的理解。作者把 KISS 跟 AR 联系在一起是因为 AR 是 用简单的接口隐藏复杂的实现 。其实更应该吐槽的是 AR 混合了太多的关注点,与这点有关联的原则应该是 Single Responsibility Principle。我也觉得 AR 操心的事情太多了,它的设计在现在来看已经过于复杂了。正因为功能强大,才导致了之后 Fat model 这种理念的兴起。
最后我还是比较赞同这句的:Rails is not simple, it is convenient。
我一直觉得一个 Convention 好不好得取决于两点,一是大众对这个 Convention 的接受程度,二是这个 Convention 符不符合直觉思考。Rails 在这两点上做的都没问题。作者主要吐槽的是 Rails 提出的 CoC 理念把很多第三方开发者都影响了,让他们制造了很多诡异的 Conventions 和滥用元编程写 DSL。但这仍然不是 Rails 的问题,也不能证明 CoC 是不好的。
是否 CoC 从广义上看是 implicit 和 explicit 孰优孰劣的问题。我觉得把符合直觉的配置变成 Conventions 能简化很多事情,其他部分不妨用 Configurations。
这确实不是个好事。但 Rails 社区也早就不这么干了,现在几乎找不到 Fat model 的文章了,反 Fat model 的文章倒是不少。
说到 Fat model 这个话题,我抛一个不成熟的观点,我觉得 Rails 对开发的简化很大程度上来自于 对抽象层级的压缩 。这点在 AR 上体现得尤其明显。这种设计在做简单的功能时会非常省事,在 model 里写几行 validation 和 callback 就把数据库事务,表单验证,业务逻辑前后的副作用(比如发送邮件)全部搞定了。但如果复杂的场景下还沿用这种开发方式就会导致 model 臃肿,业务逻辑互相缠绕等问题。究其原因,还是抽象层次的压缩导致了代码缠成一团。
但从另一个角度看,Rails 也并没有阻止开发者去加自己的抽象层。validation 和 callback 这些功能也被设计成很容易加到其他 class 里面。所以 Rails 还是蛮灵活的。只是开发者必须意识到对简单问题的处理思路(很多人理解的 Rails way)基本不可能沿用到复杂问题领域,而这点认识往往是在被坑过之后才知道的。
这部分提到的业务逻辑和 Rails 框架混杂,本质上还是没有对业务逻辑做足够的抽象,或者说对 OO 缺乏足够的理解才导致把 Rails 当做英语考试的完形填空去做。但现实中的业务逻辑是千变万化的,没有一个框架能完美地抽象出公共的模式。
其实一个引申的问题更值得思考,那就是框架到底应不应该提供架构决策?我目前的想法是框架也是系统架构的一部分,但不能完全代表整个系统的设计。这个话题也许会让人联想到 Domain-Driven Design 和 Uncle Bob 说的 Hexagonal Architecture,或者 DCI 等模式。有任何想法欢迎探讨。
这一段的论点是 Rails 默认给你了太多的东西,但这就是所有全栈框架的思路,拿这点吐槽 Rails 也是不客观的。关于全栈框架和微框架的讨论已经很多了,就不多说了。
Ruby 的 mixin 到底算不算 composition 这点真不好说。我赞同作者文章下面的评论说的,这是 composition 本身定义太模糊了。Sandi Metz 在 Practical Object-Oriented Design in Ruby 里定义的概念更清晰:
I think maybe what we're getting at here is "composition at the object graph level" rather than "composition at the object level"
最后说点总结的话,作为一个靠 Rails 混碗饭吃多年的开发者,我对 Rails 的感情是比较复杂的。它是有一些设计缺陷,但也确实带来了一批新的理念,也许现在看起来都是老生常谈,但在 07 年那个时候却是一种颠覆。另外,框架只是工具,开发者仍然应该学习一些设计能力和原则,它们跟框架理念有可能有冲突,但更多是互补的。
最后,还是推荐各位 Railser 都去看看这篇文章。有时候从反对者中也能学到不少知识,毕竟没有什么东西是完美的。太喜欢或者太讨厌都是狭隘的表现,最狂热的爱好者更容易变成最冷酷的反对者。
#2 楼 @chenge 其实我感觉正好反了。DDD 的 domain model 最开始提出并不是为了干掉 Active Record pattern,而是为了干掉贫血模型。可以看看 Martin Fowler 的 AnemicDomainModel 。我是看了这个才知道贫血模型是什么意思 感觉 DDD 还是有很深的东西可以挖掘,很多衍生概念都跟它的这种思路有关,不过近期最热的应该是 CQRS 了。
更好的做法应该是 Entity 包含一定的业务逻辑,但不包含数据库存储逻辑。如果有些操作很难以某个 Entity 为中心就单独抽一个 Service。这跟 7 patterns 里面抽象 Service Object 是基于同样的理由。
我之前有个项目做过把所有操作都抽成 Service Object 的事情,校验不在 model 里面,极少写 callback。代码是好维护很多,副作用可预测,构建测试数据会非常方便。但代码量会稍微多一点,而且有时候感觉一个 Service Object 只操作某一个 model 有点怪,不如 model.do_something()
。只是我觉得单独写一个 Object 也能起到分离关注点的作用就继续这么干了。总体来说还是利大于弊的。
这段时间在看 Domain-Driven Design Quickly,感觉 DDD 最核心的思想的还是划分 domain model,让领域概念和技术实现同步,减少系统分析师和软件工程师的沟通误解。
Repository pattern 我还挺喜欢,但目前 Ruby 圈子里的实现都有点复杂,包括 ROM 和 Lotus。唯一看得上的是 Elixir 的 Ecto,兼具严谨和灵活。
感觉 DDD 更适合大型项目,而且大型项目中也不是所有地方都要用到 DDD 的那些模式。
考虑集成第三方登录的话就是 OAuth 了,Omniauth 这个 gem 可以用用。如果自家网站也有登录注册的需求,Rails 的 has_secure_password
也挺方便。devise 太难改。
#5 楼 @harryyoung 嗯,我并没有用过这个功能,所以不了解。
#2 楼 @harryyoung AMS 有从 params 反序列化出来的方法,不过还在试验阶段。见 deserialization 。有时候自己实现一个反而简单,如果有更复杂的需求,可以参考下它的设计和源码。
@jasl 知乎上有人说不带 bar 的续航一样,带 bar 续航时间更短了,因为整个 bar 实际上是个内嵌的 watchOS。不过现在还没看到买了带 bar 的人的反馈。
@shatle 如果我没理解错,你需要从两张主要的表中筛选合并一些数据,如果你业务逻辑需要,分页是可能的,字段统一也不是问题。你可以在 SQL 层面用 AS 和 UNION 做。当然这不是唯一的做法。前段时间还有人问过 UNION 如何做分页的问题,就不多说了。
另外,作为 web 开发者,SQL 是必学的技能,不是喜不喜欢的问题。你这个说法就像是写 Rails 但不太喜欢 Ruby 一样。
横线(也就是 dash,连接符)在 JSON API 中只是一个推荐,不是强制的。它曾经是一个必须遵循的规范,但当时经过很多人的反对(具体可以查 GitHub issue),而维护者拿出的理由主要有两点:
可见这两个理由都是站不住脚的。所以最后这个就变成了推荐而不是规范强制执行的一部分了。目前也没有说哪种比其他几种更好的说法,所以这点随你喜好,比如你都可以用下划线,前后端都方便了,只是前端看着不那么协调,毕竟 JS 一般是驼峰命名的。
废话了这么多,只是想表达一点。横线不是规范,所以不用在意违背了它就是政治不正确的做法。广义一点说,世上就没有绝对正确的做法,只有适合自己的做法。
@shatle 一般统计也会在数据库层面用 SQL 做,不会全用 AR 查出来在 Ruby 层面做的。当然这只是我猜测的你的做法。你不妨说说业务场景,不然没人懂你这是什么意思
轻薄是需要代价的,重量变轻了,屏幕色域变广了,待机时间延长了,挺好的。当然也有键盘键程更短了,接口变少了这些缺点。这不是一个均衡的产品,不过第一代产品也不可能做得各方面平衡。反过来想一下,在那么薄的机身里容纳很深的键程和更多的接口本身也是现有技术很难做到的事情。
Touch bar 的设计我感觉还不错。很多人在说视线离开屏幕会影响效率。仔细想想,Touch bar 更多的是右键菜单和工具栏里的功能挪到了键盘上面。我觉得这样反而是提升了效率,毕竟快捷键是有记忆成本的,而且不是所有功能都有快捷键,更不是所有人都能盲打 F1 - F12 的。当然这是仁者见仁的事情。
至于新款还有不带 Touch bar 的…… 第一代 retina 屏出现的时候,那一代 MacBook 也只有 15 寸是 retina 的,下一年就普及到 13 寸了,然后现在已经只有 retina 了。
AR 和整个请求耗时如此长,id IN
有 5000 条。正常页面不会查这么多数据。你们这是什么页面?
@flemon1986 正解。虽然可以改默认行为,但用自定义的格式更灵活且无风险。补个文档 ActiveSupport::TimeWithZone#to_s
偶尔关注一下 Ember,感觉很多 2.0 时宣布的东西现在都还在实验阶段,engine, routable component, fastboot 都是如此。现在这些东西稳定性如何?
大点的文件使用 IO 对象应该是最好的做法了,不用操心内存占用。具体场景中 csv 读取后一般就是数据库操作,比如插入数据。这时可以采取一些并发方式写入数据库,进一步优化整个过程,不过那是另一个问题了。
最简单的方式就是 live reload。可以看看 Browsersync ,command line 的方式应该不用写代码,不过你要装个 node + npm。Ruby 也有相关的 live reload 的方案,可以搜一下。
如果都不想折腾…… 找找你的 IDE 有没有 live reload 之类的插件。
Webpack v2 现在稳定么?
Omniauth 号称 OAuth 框架,它做的把各种供应商(provider)的 OAuth 流程统一的事情(其实不仅仅是 OAuth,但大多数插件的验证流程都是 OAuth)。uid, name, info 是 Omniauth 统一规范的格式。这些数据普遍情况下都是各个插件自己调用供应商的 API 获取数据,然后把值填进去的。一般这个步骤在获取 access token 之后(OAuth 流程的最后一步)。
如果是写插件的话,Omniauth 已经负责了大部分 OAuth 的流程,架子都跟你搭好了,附上我之前写过的一个,因为太简陋了我都没敢做 gem,直接扔在 lib 里面了。
module OmniAuth
module Strategies
class SomeService < OmniAuth::Strategies::OAuth2
option :name, 'xxx'
option :client_options, {
site: 'https://xxx',
authorize_url: 'https://xxx',
token_url: '/v1/token.json',
}
uid { raw_info['id'] }
info { {'name' => raw_info['name']} }
extra { {'raw_info' => raw_info} }
private
def raw_info
@raw_info ||= access_token.get('/v1/companies/me.json').parsed
end
end
end
end
顺便说一句,在浏览器端解码 token 并不是一件困难的事情。比如 auth0 的这个 angular-jwt 。因为它做的就是解码 token 获取数据的事情,所以 API 设计就没有任何 key 让你传。给个 token 它就把数据告诉你。
#10 楼 @harryyoung 重新看了一下,不行。拿官方例子来说:
require 'jwt'
require 'base64'
payload = { data: 'test' }
token = JWT.encode payload, 'my$ecretK3y', 'HS256'
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.ZxW8go9hz3ETCSfxFxpwSkYg_602gOPKearsf6DsxgY"
# 取 xxx.yyy.zzz 的中间段,base64 decode 。无需 secret key
Base64.decode64 token.split('.')[1]
# => "{\"data\":\"test\"}"
encode 不是 encrypt。你说的那些算法只是用来数字签名的,目的是确保 token 携带的数据没有被篡改。
需要频繁测试,并且手动测试成本大于自动的时候写。我觉得测试的优势就是 快速,可重复,精确,低成本 。
@kai209209 国内都用华为么?
突然想到个类似 JWT 的办法,不需要额外的存储系统和类库,就是 ActiveSupport::MessageEncryptor
。它也是把数据 base64 化成一段字符串,但它可以用一个 key 去加密。key 你可以存在任何地方,比如 secrets.yml 里。这可能是成本最低的方案了。