这个事例说起来相当简单。不过小中见大,它大致体现了我和pair在DRY vs. 简单性上的差别,和那个“这样代码重用”里面的例子体现了同样的分歧。
目标是重构下面的测试代码:
public void test1() {
Account acct = new Account();
acct.setName("test");
acct.setType(TypeEnum.Type1);
acct.setActive(true);
Result result = runSomeApi(acct);
assertEquals("test", result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(1, arr.length);
assertEquals("type1", arr[0].getName());
}
public void test2() {
Account acct = new Account();
acct.setName("test");
acct.setType(TypeEnum.Type2);
acct.setActive(true);
Result result = runSomeApi(acct);
assertEquals("test", result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(1, arr.length);
assertEquals("type2", arr[0].getName());
}
public void testNull(){
Account acct = new Account();
acct.setName("test");
acct.setType(null);
acct.setActive(true);
Result result = runSomeApi(acct);
assertEquals("test", result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(2, arr.length);
assertEquals("type1", arr[0].getName());
assertEquals("type2", arr[1].getName());
}
首先通过发现重复,我和pair都同意下面的重构:
private String name = "test";
private getResult(TypeEnum type) {
Account acct = new Account();
acct.setName(name);
acct.setType(type);
acct.setActive(true);
return runSomeApi(acct);
}
public void test1() {
Result result = getResult(TypeEnum.Type1);
assertEquals(name, result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(1, arr.length);
assertEquals("type1", arr[0].getName());
}
public void test2() {
Result result = getResult(TypeEnum.Type2);
assertEquals("test", result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(1, arr.length);
assertEquals("type2", arr[0].getName());
}
public void testNull(){
Result result = getResult(null);
assertEquals(name, result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(2, arr.length);
assertEquals("type1", arr[0].getName());
assertEquals("type2", arr[1].getName());
}
分歧来自于下一步,我认为重构到下面这样就够了:
private String name = "test";
private getResult(TypeEnum type) {
Account acct = new Account();
acct.setName(name);
acct.setType(type);
acct.setActive(true);
return runSomeApi(acct);
}
private void assertInvariants(Result result) {
assertEquals("test", result.getName());
assertTrue(result.isActive());
}
private void verifyWhenTypeIsNotNull(String expectedType, TypeEnum type){
Result result = getResult(type);
assertInvariants(result);
ResultType[] arr = result.getTypes();
assertEquals(1, arr.length);
assertEquals(expectedType, arr[0].getName());
}
public void test1() {
verifyWhenTypeIsNotNull("type1" TypeEnum.Type1);
}
public void test2() {
verifyWhenTypeIsNotNull("type2", TypeEnum.Type2);
}
public void testNull(){
Result result = getResult(null);
assertInvariants(result);
ResultType[] arr = result.getTypes();
assertEquals(2, arr.length);
assertEquals("type1", arr[0].getName());
assertEquals("type2", arr[1].getName());
}
而pair重构到这一步后,仍然觉得不够DRY,坚决推进下一步的重构:
private String name = "test";
private getResult(TypeEnum type) {
Account acct = new Account();
acct.setName(name);
acct.setType(type);
acct.setActive(true);
return runSomeApi(acct);
}
private void verify(String[] expectedTypes, TypeEnum type){
Result result = getResult(type);
assertEquals("test", result.getName());
assertTrue(result.isActive());
ResultType[] arr = result.getTypes();
assertEquals(expectedTypes.length, arr.length);
for(int i=0; i<expectedTypes.length; i++) {
assertEquals(expectedTypes[i], arr[i]);
}
}
public void test1() {
verify(new String[]{"type1"} TypeEnum.Type1);
}
public void test2() {
verify(new String[]{"type2"}, TypeEnum.Type2);
}
public void testNull(){
verify(new String[]{"type1", "type2"}, null);
}
我的观点:
最后这一步的重构,是以简单性换取非常细微的DRY,并不划算。最好的情况,也就是和重构前半斤八两,所以不值得投入时间来做。而重构前的代码更能灵活适应变化。
比如,经过分析,我们完全可以仅assertEquals(2, arr.length)而不用分别对每个元素进行assert了(同样的逻辑在别的测试中已经覆盖了,而且,测试代码决定"type1", "type2"的顺序也加大了测试和被测试代码的耦合,这个顺序本来是无所谓的。)这个变化在重构前很容易,只要从testNull()里面删掉两行assertEquals就可。而重构后的代码则需要更复杂的逻辑控制才能达到这个目标。
那么,你怎么看?
分享到:
相关推荐
程序员应当把时间花在创造性和创新工作上,而非简单的重复劳动。为了达到DRY原则,程序员们采取多种手段,包括开发函数库、框架、脚手架,以及采用面向对象编程的继承和重用机制。在更高级的层面,代码管理和SOA...
在实际应用中,根据需求平衡清晰性和便利性,可能需要适度妥协。 6. **You Ain’t Gonna Need It (YAGNI)** YAGNI原则主张只实现当前需要的功能,避免过度设计。过度设计可能导致项目复杂性增加,增加开发成本,并...
55. **设计的灵活性与稳定性平衡**:在追求灵活性的同时,保证系统的稳定性。 56. **设计的可维护性与性能平衡**:在满足性能需求的同时,保证代码的可维护性。 57. **设计的抽象级别**:选择合适的抽象级别,既能...
然而,在追求速度的同时,也应平衡其与代码可读性和可维护性的关系。 ### 秢四:Small Code(精简的代码) 代码量的多少往往反映了代码的效率和复杂度。通过消除冗余代码,合并重复逻辑,可以使代码更加紧凑,同时...
在选择设计方案时,要考虑开发成本、易用性和可维护性之间的平衡。 11. **异常处理策略**:尽管幂等框架需要处理多种异常情况,但为了简化开发,部分异常可能交由业务系统或人工处理,降低框架本身的复杂度。 通过...
第三条:代码简洁性与效率的平衡 追求代码行数少并不总是意味着效率高。过于紧凑的代码可能导致难以理解和维护。适当拆分逻辑,增加可读性,比盲目追求代码行数更为重要。 第四条:重视代码技巧和重构 良好的编程...
1. **重构的基本原则**:理解重构的目的,学习何时进行重构,以及如何平衡重构与添加新功能之间的关系。 2. **代码异味与坏味道**:识别代码中常见的问题,如重复代码(DRY原则)、过长方法、复杂条件表达式等,...
- **代码质量与开发效率之间的平衡**:虽然AI辅助工具可以显著提高开发效率,但同时也需要关注代码质量的下降风险。这要求开发者和团队采取措施,比如加强代码审查流程,确保代码符合项目标准。 #### 对策建议 ...
应根据实际情况平衡规范化与性能之间的关系。 3. **过度使用设计模式**:设计模式是解决问题的优秀实践,但不适用于所有情况。盲目套用设计模式可能导致代码复杂度增加,反而降低了代码的可读性和可维护性。 4. **...
- 树:二叉树、平衡二叉树(如AVL树、红黑树)的基本概念和操作。 - 排序算法:冒泡排序、选择排序、插入排序、快速排序、归并排序等的原理及时间复杂度分析。 - 查找算法:线性查找、二分查找、哈希查找的应用...
- **提高代码质量的原则**:这部分内容讨论了提高代码质量的重要性,并介绍了一系列原则,如DRY(不要重复自己)、KISS(保持简单傻瓜化)等,以及如何在实践中应用这些原则。 #### 四、结论 《敏捷技能修炼:敏捷...
- 避免重复代码(DRY原则):任何信息都不应有多余的表示,避免复制粘贴代码。 4. **重构步骤**: - 识别问题:通过代码审查,性能分析等找出需要重构的部分。 - 编写测试:在重构前,确保有覆盖重构部分的测试...
通过这三个部分,学生可以学习到与日常生活时间安排、日常活动和简单情景对话相关的词汇和表达。 在A部分的Let's talk中,张鹏和佩德罗的对话展示了不同国家(中国和西班牙)的作息时间差异。他们讨论了早上放学、...
1. **数据结构与算法**:ThoughtWorks笔试可能会涵盖数组、链表、栈、队列、树(二叉树、平衡树如AVL或红黑树)、图等基本数据结构。同时,也会涉及排序(冒泡、选择、插入、快速、归并、堆排序等)、查找(线性、二...
10. **设计原则**:如KISS(保持简单,傻瓜)、DRY(不要重复自己)、YAGNI(你不会需要它)等原则,是衡量代码质量的重要标准。 以上知识点涵盖了编程题目的主要方面,每个知识点都可能对应一道具体的题目。解压...
22. **平键 (flat key)**:一种简单的连接键,平面与轴线平行。 23. **打滑 (slippage)**:由于摩擦力不足导致的相对运动。 24. **正火 (normalizing treatment)**:热处理过程,将金属加热至高于临界温度,然后在...
总的来说,Photoshop滤镜是图像编辑和创意设计的重要工具,通过灵活运用各种滤镜,用户可以实现从简单修饰到复杂艺术效果的转变,极大地扩展了图像处理的可能性。透明水滴样式的加入,为设计工作提供了更多的便捷和...
- 干接点(Dry Contact)是一种无源触点,没有电源,通常用于开关量的信号传输。干接点信号的优点在于其通用性和易于维护。 #### 二、模拟信号视频 1. **非平衡信号** - 非平衡信号是指信号线和地线之间存在电压...
此外,还可以利用Redis的持久化机制,平衡性能与数据安全。 8. **并发与事务**:Redis支持多客户端并发访问,其单线程模型保证了操作的原子性。在处理会话时,可以利用这一特性确保数据一致性。 9. **监控与故障...
24. **balance**:保持平衡,动词,维持身体或物体的稳定状态。 25. **do**:做,动词,执行任务或完成动作。 26. **try**:尝试,动词,试图去做某事。 27. **easy**:简单,容易,形容词,表示任务或情况不复杂...