各种制定标准,稳固统治地位
@chenge 忘了参数个数能不能检查了。RSpec 这个特性我就用了一段时间,后续代码都没用它。除了觉得用处有限之外,也觉得这种方式不太符合 duck typing 的本质 -- 本身定义 mock object 的行为就够了,为什么还要耦合一个类型?
Elixir 也是动态类型语言,typespec 需要依靠 Dialyzer 去做检查。
@chenge 这跟测试写没写全不是一个问题。假设 A depends on B。你对 A 做测试,把依赖 B 用 mock 替换掉了。当然 B 也有自己的测试。一个月后你改了 B 的接口。这个时候 A 的测试显示一切正常。但程序已经无法运行了。
Java, C# 等静态语言里有严格的接口依赖,碰到这种情况还会报个编译错误。Ruby 吭都不会吭一声。当然 RSpec 有一些手段可以部分预防这一点,比如 instance_double
和 class_double
只允许你 stub 已经实例/类中已经定义了的方法,不过仅此而已了,检测不到方法参数和返回值,这两个方法提供的保障就是聊胜于无。
接口改了测试当然也要改。问题是到时候还记不记得这件事?比如你是否能够记得每次改 B 的时候都去搜一下它到底被哪些模块依赖了?最后你会发现要保障模块协同工作,还是得靠集成测试。
我一般用 mock 的情况:
其实第一点也是为了提供稳定的前置条件。相比构造 8 个 model 数据才能写点测试,一个 mock 显然更低成本(偷懒)。大量的 mock 看似解耦了,但代价是没法测到多个模块协作的情况。对 Ruby 这种 duck typing 的语言来说更是灾难。有时候模块接口都改了但测试不会报错(因为依赖全是 mock)。最后往往测试全绿但项目都跑不起来。
我觉得好的测试的标准是“改动了软件行为测试就该挂掉” ,速度方面能够接受就行。
@FrankFang 原型链和祖先链确实有非常大的相似性。两者可以对比着学习。之前我用 JS 的 decorator 模拟过 Ruby 的 mixin。不过实现 include
容易, prepend
就麻烦点。
class 早在 ES2015 时就有了,Node 估计支持得稍晚一点。而且 class 只是语法糖,背后机制还是基于原型链的。它的用处是提供了一个标准的类型声明和继承的写法,以后就不用造各种非官方的轮子了。
JS 相比 Ruby 更好的一个语法特性是 destructuring (解构) ,相当于弱化版的 pattern matching,其他的貌似没啥印象深刻的。
为什么相关话题是 男人在结婚前都是忍着脾气,然后结婚后就爆发出来了么! …… 这个相关度计算不错……
Rails 已是业内性能标杆,基本每个 web 框架都会拿它来比比
@jasl 你如果写 gem 的话,完全可以定义一个 serializer API,其他的让别人折腾去
问卷其实跟自定义表单有些差别。自定义表单更类似标准的 form,区别只是每一个 item 的 key 是一个动态 id(对应静态表单中中每一项的 name),然后 value 可以多种不同的类型。具体做法取决于实现。但问卷就不那么标准。比如:
目前系统的做法其实挺简单,就是提交一个数组,每一项是答案。每个答案有各自不同的关联属性。举个例子:
[
{NodeID: "{uuid}", OptionRID: "{uuid}", OptionCID: "{uuid or null}", OtherKeys: "..."},
{...}
]
我现在在重构 (xie) 后端系统,也在思考更好的做法,不过要兼顾现在所有的功能实在很麻烦……
@jasl 嗯,是的。一个明确的结构更容易维护。分别建表完全可行,我这样就是偷懒
@jasl 嗯,我是为额外属性建立 Ecto schema 嵌套在主 schema 下面,方便 app struct 和 database row 的互相转换。大概这样:
defmodule Question do
schema "questions" do
field :type, TypeEnum
# 存放额外属性,Elixir 里为 map ,对应 Ruby 的 hash ,PostgreSQL 里面为 jsonb
field :config, :map
end
# 每题定义一个子类型,用来格式化 config 的数据
defmodule Select do
embedded_schema do
field :multi_sel, :boolean
field :min_sel, :integer
field :max_sel, :integer
end
end
defmodule ValueMark do
# ...
end
# 根据 type 选择题型相关的 schema 来转换数据
def cast_config(ques, params) do
mod = get_config_module(params[:type]) # get Select module
config = params["config"] |> transform_to_schema(mod) |> transform_back_to_map_if_valid
put_change(ques, :config, config)
end
end
用 jsonb 有两个原因:
这种方法的主要缺陷在于没法保证引用完整性,比如额外属性里有图片资源的 id,我也在考虑要不要特事特办,额外开字段存储部分数据。
问题间跳转就一言难尽了,前端流程处理可以问问 @nightire ,后端这边我们是存储了每个题对应的下一题的路径数据,因为也有额外属性不定的问题,所以也是 jsonb 存的。
@jasl 我现在在巧思负责新后端的开发。但我的场景不算是 form 的场景,只是同一个大类型的不同 object 都会有一些额外的属性,而且这些额外属性是可预测的(除了需求)而不是用户自定义的。目前是在应用层面用 hash/map 表示并存进数据库的 jsonb 字段里。
我们的问卷系统倒是跟自定义 form 有少数重叠部分,不过因为题目的渲染和验证等逻辑都是在前端实现的,后端更多的是持久化“元数据” ,所以也不存在用 Elixir 动态渲染页面的情况。
@jasl ,期待你开源相关的存储方案
说句题外话,如果使用 PostgreSQL 的话,查询 tree 结构的 children/ancestors 都可以用 WITH
(CTE) 做递归查询,稍微封装一下就可以不依赖任何库。详细信息可以看 文档 。
优点:
parent_id
。缺点:
model.relationship = another_model
会引发一些副作用,如果是转成纯 Ruby 对象就不存在这种问题。面试是很难看出一个人的真实能力的,所以很多公司不得不采取一些能够量化的标准:工作年限,过往履历,毕业院校…… 包括算法水平和笔试题其实也是量化的一部分,目的是最大程度的刷掉一批人,让有限的人力能投入到公司认为合格的人身上去。这跟吐槽了很多年的高考有点相似之处 -- 不是一个好的筛选方式,但至少是个简单易行也勉强可用的方式。
LZ 不妨思考一下如何量化自己的优势和公司的需求。前者了解自己,后者了解对方。多了解一下面试公司的要求,甚至是多个公司对你想要的职位的平均要求,然后针对性的弥补。即使面试失败也可以多问对方一些问题,比如理论知识不足说的是哪些理论知识?需要如何补充?能够量化的东西就有优化的空间,不然努力不到点子上,就浪费时间精力了。
最后,12 个星期拿来练手,真不如找个公司做事。别以学习的名义逃避求职,这才第三次呢。
偶尔关注下 Crystal,不过还是更看好 Elixir 多一些。个人感受是单纯的函数式编程并没有跟传统的 OO 有非常大的差别,相反很多理念是可以通用的。造成 Elixir 最大思维差异的是 process 的设计,以及衍生出来的 OTP 的那些东西,这都是 Erlang 特有的思路,函数式编程语言中貌似也就此一家。
喜欢 Elixir 的一个主要原因是,它是一个考虑到应用层级的解决方案,而不仅仅是提供一个语言。Mix 提供完整的项目构建和管理,ExUnit 提供完备的测试框架,Application 提供的组件化单元,Supervisor 提供的监控和重启机制(后两者都是 OTP 的范畴了),release 提供的打包和热更新…… 虽说 Elixir 很新,但得益于 Erlang 三十年的发展,各种基础组件都还比较稳固。不像其他语言要从地基开始重新做起。我觉得大多数基础设施还是需要时间积累的。
我好半天才想到“返工与遥遥”是 Rework 和 Remote
@nightire 应该没问题的。这跟你说的 npm link 其他 module 是一个意思。不过不重启 server 就更新代码我记得只有 Rails engine 才行。
gem 以 git submodule 的方式放在你的项目下,然后用 path 引用:
gem "your_lib", path: "./vendor/your_lib"
版本的话,用 git 管理比强制每次 bundle install 自动更新要更靠谱点。
用 includes
加条件也许可以:
Post.includes(:comments).where('comments.user_id = ?', current_user.id).references(:comments)
参见 Rails 文档
JavaScript 和 Elixir。Ruby 用的不多……
up 主在此 @nightire
Rails 包括其他框架的环境是起到 配置分组 的作用,从高层一点来理解,环境是按 使用场景 来分组的,毕竟每个场景所使用的配置项都差不多,指定一个环境名比设置多个环境变量更方便。
少数情况下会出现同类型的使用场景需要不同的配置项,最典型的是数据库等外部服务的配置。这个时候把少数配置抽成环境变量更加灵活。
不多评价。
先不加这种额外关联,等查 log 发现慢了之后再考虑不迟。
脸大就是有优势
是的。
最后一个 INSERT
语句应该可以用 ON CONFLICT
吧,这样可以简化一点数据对比和删除的过程?