`
ajoo
  • 浏览: 453109 次
社区版块
存档分类
最新评论

DRY与简单性的平衡

阅读更多
这个事例说起来相当简单。不过小中见大,它大致体现了我和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就可。而重构后的代码则需要更复杂的逻辑控制才能达到这个目标。



那么,你怎么看?

分享到:
评论
2 楼 fixopen 2007-08-15  
我同意gigix,DRY是指对一件事情说并且只说一次。
程序里面如果发现某些代码是重复的,那么应该区分出来是两件事情还是一件事情,如果因为某些原因,这两件事情看起来似乎很像,但逻辑上仍然是两件事情,不要试图去消除这种重复,除非能在更高层次把这两件事情归并为一件事情。
1 楼 gigix 2007-02-16  
在我看来,问题的关键就在于“verify”这个名字。你抽取一个方法却发现很难给它起一个更漂亮的名字,这本身就说明这些代码只是偶然地类似,而不是有什么内在的业务逻辑。强行抽取这种偶然类似的代码,是对模块内聚性的破坏。这一步的调整不是重构,因为它没有让软件的质量提升。

所以我的建议是等一等:等到重复代码真正出现的时候、给未来的添加代码或者修改代码带来困扰的时候再重构。例如你们的这个例子,最后一步重构的动机实际上是想象出来的,它并没有带来任何困扰。

相关推荐

    职业经营 1:程序员职业的本质(3).md

    程序员应当把时间花在创造性和创新工作上,而非简单的重复劳动。为了达到DRY原则,程序员们采取多种手段,包括开发函数库、框架、脚手架,以及采用面向对象编程的继承和重用机制。在更高级的层面,代码管理和SOA...

    一些软件设计的原则一些软件设计的原则

    在实际应用中,根据需求平衡清晰性和便利性,可能需要适度妥协。 6. **You Ain’t Gonna Need It (YAGNI)** YAGNI原则主张只实现当前需要的功能,避免过度设计。过度设计可能导致项目复杂性增加,增加开发成本,并...

    61条面向对象设计的经验原则

    55. **设计的灵活性与稳定性平衡**:在追求灵活性的同时,保证系统的稳定性。 56. **设计的可维护性与性能平衡**:在满足性能需求的同时,保证代码的可维护性。 57. **设计的抽象级别**:选择合适的抽象级别,既能...

    写好代码十个秘诀(林斌博士)

    然而,在追求速度的同时,也应平衡其与代码可读性和可维护性的关系。 ### 秢四:Small Code(精简的代码) 代码量的多少往往反映了代码的效率和复杂度。通过消除冗余代码,合并重复逻辑,可以使代码更加紧凑,同时...

    95丨项目实战二:设计实现一个通用的接口幂等框架(实现)1

    在选择设计方案时,要考虑开发成本、易用性和可维护性之间的平衡。 11. **异常处理策略**:尽管幂等框架需要处理多种异常情况,但为了简化开发,部分异常可能交由业务系统或人工处理,降低框架本身的复杂度。 通过...

    Java开发者十大戒律

    第三条:代码简洁性与效率的平衡 追求代码行数少并不总是意味着效率高。过于紧凑的代码可能导致难以理解和维护。适当拆分逻辑,增加可读性,比盲目追求代码行数更为重要。 第四条:重视代码技巧和重构 良好的编程...

    NET 31天重构指南

    1. **重构的基本原则**:理解重构的目的,学习何时进行重构,以及如何平衡重构与添加新功能之间的关系。 2. **代码异味与坏味道**:识别代码中常见的问题,如重复代码(DRY原则)、过长方法、复杂条件表达式等,...

    2023年的数据显示了对代码质量的下行压力.docx

    - **代码质量与开发效率之间的平衡**:虽然AI辅助工具可以显著提高开发效率,但同时也需要关注代码质量的下降风险。这要求开发者和团队采取措施,比如加强代码审查流程,确保代码符合项目标准。 #### 对策建议 ...

    反对过度设计.zip

    应根据实际情况平衡规范化与性能之间的关系。 3. **过度使用设计模式**:设计模式是解决问题的优秀实践,但不适用于所有情况。盲目套用设计模式可能导致代码复杂度增加,反而降低了代码的可读性和可维护性。 4. **...

    超高频面试题.zip

    - 树:二叉树、平衡二叉树(如AVL树、红黑树)的基本概念和操作。 - 排序算法:冒泡排序、选择排序、插入排序、快速排序、归并排序等的原理及时间复杂度分析。 - 查找算法:线性查找、二分查找、哈希查找的应用...

    敏捷技能修炼(Essential Skills for the Agile Developer)

    - **提高代码质量的原则**:这部分内容讨论了提高代码质量的重要性,并介绍了一系列原则,如DRY(不要重复自己)、KISS(保持简单傻瓜化)等,以及如何在实践中应用这些原则。 #### 四、结论 《敏捷技能修炼:敏捷...

    Java代码重构要求简要汇总.zip

    - 避免重复代码(DRY原则):任何信息都不应有多余的表示,避免复制粘贴代码。 4. **重构步骤**: - 识别问题:通过代码审查,性能分析等找出需要重构的部分。 - 编写测试:在重构前,确保有覆盖重构部分的测试...

    人教版五年级下册英语课文及单词.docx

    通过这三个部分,学生可以学习到与日常生活时间安排、日常活动和简单情景对话相关的词汇和表达。 在A部分的Let's talk中,张鹏和佩德罗的对话展示了不同国家(中国和西班牙)的作息时间差异。他们讨论了早上放学、...

    ThoughtWorks笔试代码

    1. **数据结构与算法**:ThoughtWorks笔试可能会涵盖数组、链表、栈、队列、树(二叉树、平衡树如AVL或红黑树)、图等基本数据结构。同时,也会涉及排序(冒泡、选择、插入、快速、归并、堆排序等)、查找(线性、二...

    期末练习题编程题答案.rar

    10. **设计原则**:如KISS(保持简单,傻瓜)、DRY(不要重复自己)、YAGNI(你不会需要它)等原则,是衡量代码质量的重要标准。 以上知识点涵盖了编程题目的主要方面,每个知识点都可能对应一道具体的题目。解压...

    《机械设计基础》常用单词中英文对照.pdf

    22. **平键 (flat key)**:一种简单的连接键,平面与轴线平行。 23. **打滑 (slippage)**:由于摩擦力不足导致的相对运动。 24. **正火 (normalizing treatment)**:热处理过程,将金属加热至高于临界温度,然后在...

    photoshop滤镜

    总的来说,Photoshop滤镜是图像编辑和创意设计的重要工具,通过灵活运用各种滤镜,用户可以实现从简单修饰到复杂艺术效果的转变,极大地扩展了图像处理的可能性。透明水滴样式的加入,为设计工作提供了更多的便捷和...

    硬件工程师须知.pdf硬件工程师须知.pdf硬件工程师须知.pdf

    - 干接点(Dry Contact)是一种无源触点,没有电源,通常用于开关量的信号传输。干接点信号的优点在于其通用性和易于维护。 #### 二、模拟信号视频 1. **非平衡信号** - 非平衡信号是指信号线和地线之间存在电压...

    django-redis-sessions:用于将会话存储在Redis数据库中的Django的会话后端

    此外,还可以利用Redis的持久化机制,平衡性能与数据安全。 8. **并发与事务**:Redis支持多客户端并发访问,其单线程模型保证了操作的原子性。在处理会话时,可以利用这一特性确保数据一致性。 9. **监控与故障...

    新概念英语青少版1B单词表.doc

    24. **balance**:保持平衡,动词,维持身体或物体的稳定状态。 25. **do**:做,动词,执行任务或完成动作。 26. **try**:尝试,动词,试图去做某事。 27. **easy**:简单,容易,形容词,表示任务或情况不复杂...

Global site tag (gtag.js) - Google Analytics