Day 14 「不残而废」单元测试、Code Smell 与重构 - Data Class 篇


图片来源:Wikipedia

大家听过「帕拉林匹克运动会(帕奥)」吗?它是自 1960 ~ 70 年代开始,专为身心障碍者举办的国际体育赛事,於 1990 年代开始,在一般奥运闭幕後一个月内,於同一城市举办。帕奥的名字(Paralympic)据说是由 Paraplegia 与 Olympic 合并而来,在台湾曾经一度称之为「残障奥运会」,後来为了顾及身心障碍者感受,改以音译的「帕拉林匹克运动会」称之。

今天要聊的是十个 RD 有十个都曾制造过的程序坏味道:Data Class。这是个「明明四肢健全,却要自废武功」的坏习惯。可以说如果帕奥提倡的是残而不废,那 Data Class 就是「不残而废」了。

Data Class

Data Class 顾名思义,就是「只有 Data 的 Class」,简单吧!那麽,为什麽只有 Data 是一个坏味道呢?因为在物件导向的世界中,物件与物件是藉由「行为」来互动的。没有行为,物件就不会「动」,事情就没人做。然而你为了要完成工作,不可能没人做事,对吧?所以常见的做法,就是在另外的类别里面,加入一些与这些资料相关的操作,以完成任务。

举例

这里我们就不另外举例子了,我们先前其实已经举过一个「没有 Data Class 坏味道」的例子了,程序如下。反倒是各位可以试着把 getFullName 方法往外搬到其他类别上去,让 Student 只剩资料,看看这样是否在可读性上真的变差了。

@Data
public class Student {

    String lastName;
    String firstName;

    public Student(String firstName, String lastName) {
        this.lastName = lastName;
        this.firstName = firstName;
    }
    
    // 搬走试试?
    public String getFullName() {
        return firstName + " " + lastName;
    }
    

}

解决之道

总之,当遇到 Data Class 坏味道时该怎麽解决?其实也不难,就利用 Move Method 的方法,把行为搬到物件上去就可以了。

这个坏味道,笔者认爲是在「辨别」上比较需要有技巧一点,因为得看当时的场景来决定。

「只有资料的物件」一定有 Data Class 坏味道吗?

Data Class 之所以为坏味道,展现在耦合度上。一个物件的行为明明可以自己完成,却跑去请别人帮忙,如果今天资料格式有换,那帮忙的人就不得不跟着换,测试也会跟着坏,这是不太合理的事情。先前说过了,物件导向程序的物件之间沟通,应该要尽量倚赖行为,而不是资料。

然而,Data Class 一定是坏味道吗?「VO」、「DTO」难道不是一种约定成俗的模式吗?我就是只要取资料而已,我要取的资料就只是想要放在那边给 View 来拿,或直接回传给前端网页,因此而没有行为,这样难道也不行吗?

可以,当然可以。一个物件如果只有资料,没有行为,他「不一定」会有 Data Class 的坏味道。如上所述,他还得搭配一个「放在其他地方的行为」才算数。

这时,如果我们搭配 CQRS(命令查询职责分离)模式来看,就会比较清楚。Ajay Kumar 在 CQRS 一书中,有建议我们,命令指令与查询指令要从权责上分开。命令指令要让 Entity 加入,查询则不用,可以依查询者指定的样貌任意组装物件。

譬如在我们的「教务处网站系统」中,Student 代表学生,是重要、有状态的核心物件,是 Entity,会在多种 Use Case 中被使用,这时如果一个行为是学生自己可以完成的,那就让学生自己完成。譬如我们先前曾经举过的 getFullName 行为,学生自己身上已经有 last name 与 first name,当我需要 full name 时,我可以把「组装」这个行为放在 Student 身上,而不是 Use Case 上。

如果今天是一个「登入欢迎页」需要的资料,当前端来跟後端 Query 时,我大可以依照前端指定要的项目组装给它,而这个物件只需要储存资料就好,因为前端要的也只是资料而已。他不是不能有行为,只是它大部分的情况下不需要行为,这是我们不能因为它只有资料就说他有坏味道。啊它就不需要做事情你硬塞个行为给它做啥呢?

所以,不是所有只存资料的物件都有 Data Class 坏味道,还是要看状况。

延伸思考题

这里我要请读者稍微思考一下,Data Class 坏味道,是不是很容易进一步造成 Feature Envy 坏味道?为什麽?

谜之声:「你臭我也臭,大家一起臭?」

Reference

  1. Paralympic Games:https://en.wikipedia.org/wiki/Paralympic_Games
  2. Ajay Kumar, Command Query Responsibility Segregation, Independently Published, 2019
  3. 从Clean Architecture角度探讨RESTful API Contract之设计:https://tinyurl.com/3jr6rak8
tags: ithelp2021

<<:  @Day14 | C# WixToolset + WPF 帅到不行的安装包 [Windows菜单捷径]

>>:  MultiThreading and Custom extension function.

连续 30 天 玩玩看 ProtoPie - Day 29

糟糕 突然就 29 天 了。 今天来看官网上的 ProtoPie Advanced Workshop...

第 9 集:RWD 响应式

此篇会着重在 Bootstrap 5 响应式的介绍以及使用方法。 RWD 响应式网页设计 (Res...

[Day 01] 纲要

前言 HIYO!又是阿峻我啦~ 不知从何时开始,Deep Learning 跟 AI 这两个名词好像...

用 Python 畅玩 Line bot - 02:Line bot SDK

在建立好帐号之後,我们可以开始来看看 Line bot SDK,可以从 Line developer...

Leetcode 79 Word Search (JavaScript) 的问题

各位邦友好,敝人想问一下leetcode https://leetcode.com/problems...