标签: 北京软件开发公司 2025-10-11 次
时光飞逝,不是吗?我记得6年前写这篇文章第一版时,全世界还没听说过COVID-19,而我只是个有抱负的Ruby on Rails开发者,在北京心玥软件公司尽力做到最好。
如今我从普通开发者成长为团队Leader,当然学到了很多。当年我写的那些“如何写出干净代码”的经验之谈,真的帮了大忙吗?当然,它们没害处——但世界在前进,现在这些道理早已不言而喻。既然如此,我希望你准备好迎接下一轮思考啦:)。
目录
1. 听着,这很好,但我到底为什么要在意?
2. 干净代码如何帮到业务?
3. 永远把你“露营地”打扫得更干净(童子军规则)
4. 如何用Ruby写干净代码?
5. 如何测试Ruby代码的“干净度”?
6. 持续创造价值的测试
7. 结论
1. 听着,这很好,但我到底为什么要在意?
过去6年我深刻意识到:写干净代码比我最初想的更重要。我不会重复“编码是艺术”“要考虑3个月后的自己”这类陈词滥调——我们直接说点扎心的。
真相是:业务方根本不在乎你的代码有多干净。
相信我,他们几乎不会看你的代码。他们在意的只有价值——你能在给定时间内交付多少成果。
按这个逻辑,你可能会陷入尴尬:想说服业务方给你时间“重构”,但他们会问:“这能给项目带来新价值吗?也许能跑得更快,但你确定能测出来?创业初期哪有时间试错?”于是你放弃,继续写功能,直到代码变得难以维护——交付变慢、问题频发,一切都开始崩塌。
你做错了什么?怎么说服业务方给你时间写“理想中的干净代码”?为什么别人(比如童子军)做到了,你却没做到?
2. 干净代码如何帮到业务?
暂且放下技术细节,把自己代入业务CEO的角色:
你有个愿景,知道要做一款产品,要在2个月内抢占市场赚第一桶金。你快速组建开发团队,启动第一个Sprint——一切顺利,产品上线,拿到第一批用户。
但用户带来更多需求,你需要更快开发更多功能。这时怪事发生了:开发团队的交付时间越来越长。你百思不得其解。
原因很简单:你的产品已经变成了一座叠叠乐积木——动一块砖,整座塔都会倒。软件开发不是短跑,是马拉松。干净代码能帮你保持稳定节奏,而不是冲刺一段后就彻底无法收尾。
举个真实例子:几周前我们彻底改版了官网。它用RoR开发,配定制后台,团队全程注重干净代码。结果?新官网SEO优化到位、移动端友好、用户体验佳——我们已经看到用户停留时间变长、流量增长,未来维护和扩展也会轻松得多。
3. 永远把你“露营地”打扫得更干净(童子军规则)
听着:没人能第一次就写出完美代码——因为完美不存在。你无法预知未来的业务决策,也不知道要做哪些权衡。所以写干净代码是持续的努力。
每次评估任务时,先看你要处理的代码,把需要的小重构算进任务里——不用搞大重写,小事能带来巨大改变:
• 偶尔提取一个方法;
• 改善变量命名;
• 把小功能拆成独立对象,作为协作者注入(减少耦合)。
当然,这不是说你不用在意第一次写的代码——你总该写出当前最好的代码,但一定要记住:不久后你会需要扩展或修改它。
最后这个问题,其实可以归结为:
4. 如何用Ruby写干净代码?
干净代码的核心是:你不害怕修改它。你不该害怕自己的“孩子”。要做到这点,重视你的测试套件。
关于写好测试的资源很多,但对我而言最重要的一条建议是:别过度DRY你的测试。
你见过这样的Spec吗?打开文件,顶部20个let语句,每个测试只有一行,所有setup都藏在before回调里?我见过,也写过——曾经的我拼命追求“极致DRY”,直到后来反思:
过度DRY的Spec,真的比“行数多但清晰”的纯Ruby代码更好读、更好维护吗?
如果继承一个项目,你更愿意看冗长但清晰的Spec,还是简短但混乱的?
答案不言而喻。我们还是让代码说话吧——比如下面这个ExampleImportService的测试:
RSpec.describe ExampleImportService do let(:user_to_update_hash) { { email: "test@example.com", first_name: "Jane", last_name: "Doe" } } let(:new_user_hash) { { email: "test2@example.com", first_name: "Jack", last_name: "Doe" } } let(:mock_csv_string) { instance_double(String) } # String的实例替身 before do create(:user, email: "test@example.com", first_name: "John", last_name: "Doe") # 创建初始用户 # 模拟文件读取和CSV解析 allow(File).to receive(:read).and_return(mock_csv_string) allow(CSV).to receive(:parse) .with(mock_csv_string, headers: true) .and_return([new_user_hash, user_to_update_hash]) subject.call # 调用被测方法 end describe "#call" do it "creates new user" do expect(User.find_by(email: "test2@example.com")).to be_present # 期望新用户存在 end it "updates existing user" do expect(User.find_by(email: "test@example.com").first_name).to eq("Jane") # 期望用户名字更新 end end # 更清晰的写法:提取mock逻辑,避免重复setup describe "#call" do def mock_import(import_data) # 封装模拟导入的工具方法 mock_csv_string = instance_double(String) allow(File).to receive(:read).and_return(mock_csv_string) allow(CSV).to receive(:parse) .with(mock_csv_string, headers: true) .and_return(import_data) end it "creates new user" do new_user_hash = { email: "test2@example.com", first_name: "Jack", last_name: "Doe" } service = described_class.new mock_import([new_user_hash]) service.call expect(User.find_by(email: "test2@example.com")).to be_present end it "updates existing user" do create(:user, email: "test@example.com", first_name: "John", last_name: "Doe") user_to_update_hash = { email: "test@example.com", first_name: "Jane", last_name: "Doe" } service = described_class.new mock_import([user_to_update_hash]) service.call expect(User.find_by(email: "test@example.com").first_name).to eq("Jane") end end end
5. 如何测试Ruby代码的“干净度”?
干净代码的“干净”,本质是易维护、易扩展。而测试是最好的“体检报告”——它能暴露代码的问题。
读测试时,我会先看两件事:
1. Mock的数量:allow/expect to receive越多,说明被测对象的协作者越多。协作者多不是坏事,但要问:我能替换它们吗?耦合度有多高?
如果Mock多到爆炸,大概率是缺少依赖注入——简单说,就是没从外部传入协作者。依赖注入能让你把对象拆成“乐高块”,未来扩展时只需组合即可。
2. Setup的复杂度:如果每个测试都要写一堆mock和初始化代码,说明你的方法可能过度接收参数(原始类型痴迷),或者缺乏系统边界的验证/转换。
6. 持续创造价值的测试
好的测试不仅能验证功能,还能帮你优化架构。比如:
• 如果setup太长,可能要提取参数对象;
• 如果多个测试在检查同一场景的不同数据类型(数组、哈希、nil),可能要加数据验证;
• 如果Mock太多,可能要引入依赖注入。
记住:测试越简单,代码越干净。
7. 结论
我想让你从这篇文章里只带走一件事:
写干净代码是持续的努力——它不会一次成功,也不会一夜见效。虽然难说服业务方,但它的价值会在未来慢慢浮现。
记住童子军规则:永远把你接手的代码,留在比你发现时更好的状态——哪怕git blame还没轮到你:)。
如果你觉得用Ruby on Rails写干净代码游刃有余,看看我们的开放职位——也许你就是我们要找的人!