确实网络上不好讨论,项目里的代码枝枝蔓蔓的,要放论坛上,不知道切多少出来合适。上下文不完整的话容易误解。
“大”还是挺成问题的,尤其是改的代码还不是自己实现的情况下
可以一小步一小步的来,而且你举的例子,似乎只是方法的移动,看起来并没什么风险。
另一方面,如果遇到一个问题,不是选择正面解决,而是选择绕过去,就欠下了一次技术债,未来还是要还的。
我是在聊我写代码时的意识,不知道有没有扯远。
打算换个思路实现了,实际上我是想让 Base 有不同的处理模式,不同模式在 Base 中处理的分支不一样,但不同模式的 exec 实现内容是一样的。给 Base 添加模式属性进行判断,就是需要改动的 Base 代码比较多
然后你说的给 Base 里面加条件来判断,我感觉路有点走反了。本来应该是选择用 OOP 的多态特性来替换掉重复的类型条件判断的。因为多态本身在执行时就进行了类型判断,这样能省掉许许多多重复的条件判断代码。否则容易在一个类里积累起来很多带相同条件判断的方法。
但你的意思似乎是反过来,把子类里的代码都移入父类,然后在父类里加上类型标志来做判断。
我觉得改动大不应该成为问题。
一个事情要不要去做,我认为可以通过一个方法判断:这个事情如果不用花时间就能改好,那么我们改还是不改?如果答案是“改”,那实际上花时间改它是对的。要是我的话,甚至我可能会用业余时间去改它。但我发现大家很多时候是在这上面纠结。
抽到 module 中不影响 BizA
和 BizB
继承 Base
吧?其它代码继续放在 Base
里就好了。
如果 module 只是 BizA
和 BizB
共有的,那就只给它俩用这个 module 就完了。
我的经验是,不要企图靠工作提高自身或者实现自己的抱负。大多数时候,工作就只是用时间换钱。实现自己的抱负的工作不多,或者说极少。真想尝试 TDD,就自己去试。提升自己能力的也只能是自己。
另外,现在 Ruby 行情似乎真的不好,写 Ruby 的本来就在减少,再加上经济下行,更不好找。找工作不妨试试前端,或者其它机会更多的语言,也不妨碍你继续喜欢 Ruby,还能拓展能力。
是的,好像蛮多人对这词存在误解,可能多数情况下,大家都觉得重构只是针对大量遗留代码的。
还有“非得有测试才能重构”的认识似乎也在不小的程度上影响着重构的实施,给重构加了不小门槛。我是觉得有测试肯定更好,但没测试也不是不行。
我比较想知道有没有人真的这么干过。
重构可能分为两种情况,一种是边写边重构(随时保持代码整洁),这样自然积累不起来多少腐烂的代码。
另一种是写的时候不管不顾,后期实在受不了了再重构,这属于重构遗留代码。
如果是后一种,这时候代码似乎往往比较难测试。写的时候如果都不花时间重构,并对更干净的代码编写更容易写的测试,我比较怀疑在更难测试的时候,这个人会愿意花更多精力和时间来先补测试。
但实际上后一种情况还是有救的。比如 3 楼的这段代码:
def calculate_advertisement_cost(platform_info)
# ... 前面省略
audience_factor = case platform_info.daily_active_users
when 0..10000 then 0.8
when 10001..100000 then 1.0
when 100001..1000000 then 1.2
else 1.5
end
adjusted_cost = base_cost * audience_factor
# ... 后面省略
end
我把它放入 PlatformInfo
:
def calculate_advertisement_cost(platform_info)
# ... 前面省略
adjusted_cost = base_cost * platform_info.audience_factor
# ... 后面省略
end
class PlatformInfo
def audience_factor
case daily_active_users
when 0..10000 then 0.8
when 10001..100000 then 1.0
when 100001..1000000 then 1.2
else 1.5
end
end
end
这就是一次重构了,这样的改动,你觉得需要测试吗?这就是“只改变了 calculate_advertisement_cost
结构,而不改变它的外在行为(逻辑)”。这次重构的效果是缩短了一部分 calculate_advertisement_cost
方法,这整个方法我可以继续这样一小步一小步改下去。如主帖所述,所有的重构都是这么不起眼的小步骤。重命名一个方法或属性,也都算一次重构。把三五行代码提取到一个方法里,也是一次重构。这就是所谓的“针对组成整个大问题的那些小问题一个一个来解决”,最终的效果都是积少成多而来的。所以主帖中一直在提“不起眼”,“小毛病”,“解小结”,“小决策”,“防微杜渐”。
不过在我重构遗留代码的经历中,确实有过把代码改出 bug 的时候,但那全都是因为我不小心改变了代码逻辑。比如我曾经很自信的在重构代码时顺手把 a.nil?
改成了 a.blank?
,多数情况下这是没问题的。但在个别时候,用 blank?
会排除掉一些本该考虑到的情况,比如空字符串,这里用 nil?
是最合适的,因此这个改动就导致运行时出 bug 了。
但这已经不是重构了,这是改逻辑了。翻遍重构手法你会发现,重构是用各种方法把代码结构整理清晰,完全不涉及修改逻辑。只要你在重构代码的过程中不像我一样手贱去修改逻辑,是改不出 bug 的。
出 bug 还有一种情况,就是代码没有改全。比如对某个 public 方法重命名,调用它的地方有好几处,不小心漏改了一处。这属于重构步骤本身没执行完整。这种情况我一般是靠全局搜索代码来一个一个修改,多搜几次,多确认几遍。这一点,像 Java 这样的语言有 IDE 辅助会方便得多。
哦,你说的是在调用方法的时候有记忆负担?如果是指这种负担,我的第一个反应是靠给这个方法和参数重新起个更合适的名字来解决。
我在前面的回复中更关注的是进入这个方法之后,对方法实现本身的理解,这个过程中上下文的记忆负担。这可以靠缩短代码行数,把代码分散到不同的抽象概念中解决(比如把其中的五六行代码单独提取到另一个方法,这可能是最常见的抽象)。而参数的数量对方法带来的复杂性,在我看来主要也是在这个方面。
但是我通常不会把方法调用时的记忆负担,用重新组织类的结构这样的方式来解决。
那要是没有测试怎么办呢?
这个我会看情况,如果方法行数短,放在 A
类里也无所谓。
class MemberOrder
def apply_festival_discount(discount)
member_price = @amount * @base_discount
if @amount >= discount.threshold
member_price -= discount.bonus_amount
end
return member_price.round(2)
end
end
如果行数长,我会考虑拆分。而拆分又分 3 种情况:在当前类中拆分,在参数所在的类中拆分,在新类中拆分。
需要新建类来拆分的,就是我上面这个例子:即有当前类属性和方法的深度参与,又有参数所在类的方法的深度参与。我换一个长一点的代码:
# 请忽略各种硬编码等等与讨论主题不相关的 bad smell,例子是我让 AI 按我的要求临时生成的
class MarketingCampaign
def remaining_budget_ratio
(@total_budget - @used_budget) / @total_budget.to_f
end
def calculate_success_rate
case @campaign_type
when "seasonal" then 0.8
when "flash" then 0.6
when "normal" then 0.4
end
end
def budget_adjustment_factor
if remaining_budget_ratio > 0.7
1.2
elsif remaining_budget_ratio > 0.3
1.0
else
0.8
end
end
def calculate_advertisement_cost(platform_info)
# 基础广告费计算
base_cost = platform_info.base_price
# 根据平台受众规模调整费用
audience_factor = case platform_info.daily_active_users
when 0..10000 then 0.8
when 10001..100000 then 1.0
when 100001..1000000 then 1.2
else 1.5
end
adjusted_cost = base_cost * audience_factor
# 根据活动成功率调整费用
success_factor = 1 + calculate_success_rate
adjusted_cost *= success_factor
# 根据预算情况调整费用
adjusted_cost *= budget_adjustment_factor
# 根据平台评级调整费用
rating_factor = case platform_info.platform_rating
when "S" then 1.3
when "A" then 1.2
when "B" then 1.1
when "C" then 1.0
end
adjusted_cost *= rating_factor
# 根据广告位置调整费用
position_factor = case platform_info.ad_position
when "homepage" then 2.0
when "search_result" then 1.5
when "recommendation" then 1.3
when "category_page" then 1.2
end
adjusted_cost *= position_factor
return adjusted_cost.round(2)
end
end
代码没必要读完,主要看的是:calculate_advertisement_cost
中即有参数 platform_info
的 base_price
,daily_active_users
,platform_rating
等方法,也有本类的 calculate_success_rate
, budget_adjustment_factor
等方法。这时候你说的 AxB
就很好用了:
把 MarketingCampaign
的实例和参数 platform_info
传入新类 CalculateAdvertisementCost
的构造方法,当作新类的成员变量 @marketing_campaign
和 @platform_info
。然后把 calculate_advertisement_cost
里所有的内容全移过去,再在新类里对其进行拆解。
class CalculateAdvertisementCost
def initialize(marketing_campaign, platform_info)
@marketing_campaign = marketing_campaign
@platform_info = platform_info
end
def calculate
# ...
end
end
这样的话,@marketing_campaign
和 @platform_info
作为新类的成员,作用域是整个类,在整个 CalculateAdvertisementCost
里都可见,拆分就容易得多了。我想你说的“這些 property 的有效性是 unconditional 的”也是这个意思?
但再回到前面短一点的那个例子,你说非要把它移到 AxB
里行不行呢?我觉得也是可以的,也很赞同。但同时我觉得不移也行。在这种比较边缘的情况下,我自己会有一种选择,要是同时别人用了另一种选择,我也没意见,因为我自己有时候也会选择另一种。我觉得写代码很多时候存在这种情况,你即可以进一步,也可以退一步。
不过如果你的意思是方法一个参数都不能有,不分情况,那在我看来又似乎有点过头了:
def apply_festival_discount(discount)
member_price = @amount * discount
if @amount >= 1000
member_price -= 50
end
return member_price.round(2)
end
像这种只有 1 个简单数值(非对象,和前面的例子不一样)参数的方法,代码还是挺正常的,我不会去重构它。
但如果有 2 个参数或以上,我会开始注意它:如果这个方法的计算步骤多,那这里的参数带来的坏处跟临时变量差不多——会把方法越拉越长,长方法总是会让人觉得复杂,所以在这个例子里,需要考虑把它变成另一个类的成员变量,然后再进行拆分。
大的知识框架看 guide 或者书(比如滑板书 AWDWR)。具体的细节查 API 文档:https://api.rubyonrails.org 或者 https://apidock.com
其实如果写代码时有职责划分的意识的话,代码写着写着你可能会突然发现:我正在写/准备写的这些代码应该是谁谁谁的职责。如果是 Rails 的,就去 Rails 的 API 里翻一翻。如果是 Ruby 的,就翻 Ruby 的 API。如果属于用到的第三方库或者框架,就去翻相应的 API。找到了就直接用,实在找不着就继续自己写。
09 年左右环境好,有公司招 Ruby,也有人找 Ruby 职位,无论是职位还是人才,数量都在增加。现在似乎是恶性循环,企业找不到人,换语言;新人找不到工作,换语言。
其实我觉得在一个本就小众的编程语言社区宣传其它编程语言,带走不少人,对这个社区伤害挺大。
说回正题,之前搞 Java,搞了 3 年。当时在研究所谓的贫血模型、充血模型这些概念,一直不太能理解。我看 JavaEye 上面几位前辈说 Rails 天然充血,就买了本 Ruby 镐头书回来啃,想从 Rails 这边借鉴一下思想。只是没想到一下被 array.find{|x| .... }
这样简单的代码吸引了,然后辞职回家买了几本书,学 Ruby, Linux, HTML/CSS/JavaScript。过程中认识了 @hooopo ,时常交流。然后 2010 年 2 月他给我介绍了份工作,就这样开始了。
这么吓人… 不行注册个 1000 元的公司好了
我做了个个人工具,隐藏在子域名里。首页就是个静态页面。
前端作为接口使用者,在接口设计上没有话语权,就这样。
五笔和表形码,差不多都是这个思路吧。
kimi 可以。而且 AI 的翻译相对比较准确流畅。
看样子要学着 Java 来搞各种“层”,培养照猫画虎工程师。
它已经告诉你答案了。
现在连换一门语言写都能被叫作重构了。
TDD 感觉更像一种设计手段,强迫你写出容易测试(易用)的接口。
文中对“为什么要测试驱动开发”的回答,其实只是在回答“为什么要写测试”。
另外测试不是重构的前提,没有测试也可以重构。
最后多数人嘴里所谓的“重构”其实不是重构,重构其实都是些很小步很细碎的代码移动、提炼等等操作。重构严格按步骤来的话,本身完全不改变程序逻辑,原来逻辑是错的重构后还是错的,原来是对的还是对的,所以其实也就无所谓测试。
还有写不出来 10 行以内的简单方法可能也容易依赖这些特性吧。毕竟代码写得复杂,脑容量就不够了。
一直 Vim + NERDTree
最近也在被强迫使用 TypeScript。Ruby 和 JavaScript 用久了,总觉得 强类型(被博客带歪了) 静态类型提供的那些约束本该是程序员自己的意识,主动思考还是被动思考的差别罢了。
我觉得程序员写代码时的思维应该是主动的、清醒的、活跃的,而不是被动的、怠惰的。
另外,没看明白什么叫“不能正确处理代码接口”?
等你刚问完、讨论完、开完会,他们会问你“快开发好了吧”。
Struts 1 默认用的 .do,不过这个其实 Servlet 都可以随意配置,改成 .html 都行。
另一条路子:独立开发,爱用啥用啥。
读书人的事,能算偷么?别在意这些细节。
根据本帖讨论的内容搜到一个帖子:http://129.226.226.195/post/24983.html 为什么将 Python 用于高性能/科学计算(而不是 Ruby)?
不过读着像是机翻。