代码诊所

简介: 代码诊所

几年前,我有机会负责一个项目的咨询。团队很小,目标是对旧有系统的后端用Java改写,而团队的开发人员全为C程序员。我的工作职责是负责项目设计、开发,以及担任项目开发过程敏捷化的教练,并培养Java开发人员。

C程序员的一个特点是基本不具备面向对象知识,在初步掌握Java语法之后,写出来的代码还是过程化的代码。团队开发人员的现状就是:没有Clean Code的意识,不知道何谓TDD与重构,写出来的Java代码质量很糟。如果从项目开初不针对这一问题进行有效的防治,就可能导致整个代码库陷入泥沼之中。

为此,我要求在每日站会之后及时开展了代码评审活动。评审过程中,只能以我为主导,帮助大家发现代码的坏味道。从一开始,具有坏味道的代码可谓俯拾皆是,就像我们每天都在呼吸污染了的空气一般不可避免。要净化空气任重而道远,要让团队成员写出好的代码,同样任重而道远。

得有药方才行。

于是我当起了诊治代码疾病的医生。为了更容易传播医疗知识,我在团队工作室的墙角落,开了一个小小的诊所,广而告之——“每日一贴,包治百病”。刚开张时,诊所门面还没装修好,所以直接找了个白板开出了一个药方:

image.png

个人认为,这些处方不仅仅对于当时的客户团队有疗效,可能也适合大多数开发团队。几年过去了,我把这些处方分享出来,也算是一个小小的总结吧。先来看看这些处方吧。


第一条:应随时保持架构的清晰与简单:统一所有查询为Repository。


项目其实并不需要访问数据库,而是通过远程的Telnet(或其他协议)去访问前端的设备。然而,我们可以借鉴DDD中资源库的这个隐喻。至于提到的架构,则是我在架构设计时参考了DDD的分层逻辑架构。

image.png

为保证架构的简单与清晰,我做了一些“一刀切”的简化原则:例如对Repository和Service的定义。通过Telnet等网络协议获取设备信息的功能不妨看做是对DB的查询。因而,诸如NodeConfigureGetter这样的类就应该统一命名为NodeConfigureRepository。

该处方的主要目的是为了保持代码的一致性,若不加以规范,就会出现Getter、Finder、Query等各种不统一的类名后缀,让人眼花缭乱。


第二条:依赖注入(对象之间的协作)


很多OO初学者并不能理解依赖注入。我的一个办法是让他们从可测试性的角度出发。例如,倘若在NodeConfigureRepository类中直接实例化了TelnetService(这个类提供连接、登录、执行命令等与Telnet有关的操作),那么该怎样在不需要Telnet环境的基础上为NodeConfigureRepository编写单元测试呢?解决不了这样的问题,就说明设计的可测试性不够好。

解决方案就是依赖注入。当时的项目并未引入第三方IoC容器,原因在于项目的Jar包需要和另一个系统协作,并驻留在Flash中。容量有限,不允许引入太多第三方包,保证Jar包的精悍。


第三条:方法名体现意图。


这个问题是许多开发人员都容易犯的毛病,尤其对于面向过程设计的程序员而言,很少会站在对象的角度去思考方法(即行为,准确地说,从设计的角度讲应该是对象承担的职责)。例如在NodeConfigureRepository类中,开发人员定义了getNodeConfigure方法,但返回值却是void:

public NodeConfigureRepository {
    private NodeConfigure configure;
    public NodeConfigureRepository(NodeConfigure configure) {
        this.configure = configure;
    }
    public void getNodeConfigure() {
        getMasterLogicBoard();
        getMasterIp();
        getEnvId();
        getMasterSlot();
        getSlaveIp();
        getSlaveBoardTypeAndStatus();
    }
}

这个方法调用的诸多私有方法实则都是对构造函数传入的NodeConfigure进行数据收集。这样的定义不仅让代码的调用者感觉怪怪的,测试也变得极为诡异:

@Test
public void should_get_main_ctrl_logic_board_type() {
    configure = Nodeconfigure();
    configureRepository = new NodeConfigureRepository(configure);
    configureRepository.getNodeConfigure();
    assertThat(configure.getMasterLogicBoard(), is(12288));
}

怎么改?

方法就是让getNodeConfigure()方法直接返回组装之后的NodeConfigure对象,并且解除NodeConfigure与NodeConfigureRepository之间的生命周期依赖。有趣的是getNodeConfigure方法内调用的私有方法。它成了一种设计的例外,因为在Java中通常需要避免直接对输入参数进行修改,并将其作为返回结果。而在这里出现的一系列方法,实则是履行对NodeConfigure对象的数据收集,因而可以定义为:

private void  collectMasterLogicBoard(NodeConfigure configure) {}
private void  collectMasterIp(NodeConfigure configure) {}
private void  collectEnvId(NodeConfigure configure) {}
private void  collectMasterSlot(NodeConfigure configure) {}
private void  collectSlaveIp(NodeConfigure configure) {}
private void  collectSlaveBoardTypeAndStatus(NodeConfigure configure) {}

我对这些方法的名称进行了修改,使其能够更好地展现其意图。于是,getNodeConfigure()就变成了:

public NodeConfigure getNodeConfigure() {
   NodeConfigure configure = new NodeConfigure();
   collectMasterLogicBoard(configure);
   collectMasterIp(configure);
   collectEnvId(configure);
   collectMasterSlot(configure);
   collectSlaveIp(configure);
   collectSlaveBoardTypeAndStatus(configure);
   return configure;}

这里实际上是Kent Beck提出的Collected Parameter模式。它是Visitor模式的简化设计。当然,我们也可以运用Builder模式对NodeConfigure对象进行组装。


第四条:同一个方法中的实现代码应处于同一抽象层次。


这其实是老生常谈了。Kent Beck在Smalltalk Best Practice Patterns一书中提到了“组合方法”模式,建议“让一个方法中的所有操作处于相同的抽象层”,即所谓的SLAP原则。在Robert Martin的Clean Code一书中也反复提到这一原则,Neal Ford在Emergent Design也有详细描述。


第五条:避免“哑对象”


这里展现的坏味道,在Martin Fowler的Refactoring一书中已有提及。在项目中,存在一些操作Xml文件的操作,并将这些Xml文件的Element映射为了Java对象。我们没有使用Jaxb,因为对于我们有限的xml操作而言,Jaxb还是显得太重。然而,在我们的代码中,包括PackageStatusFileParser、StoragePackageGenerator、DownloadingConfigureParser等类中都存在着将Xml Element转换为PackageInfo、SoftInfo等对象的重复代码。

原因就在于我们将这些对象看做了“哑”的数据对象,而没有将这种转换行为封装到拥有这些数据的对象中(我们的转换仅牵涉到Xml,没有扩展可能,因而无需使用Visitor模式)。

除了会导致大量的重复代码之外,一旦转换逻辑发生变化,例如XmlElement增加了Attribute,就可能需要到处修改,形成所谓的“霰弹式修改”。因而需要将这些逻辑封装到对象中,例如:

public class PackgeInfo {
    public PackageInfo createFrom(Elment element) {}
}
PackageInfo packageInfo = PackageInfo.createFrom(element);

代码虽为细节末道,看似微末,然而在项目开始之初不加以规范与约束,而任其腐化蔓延,最终造成的苦果还是会反击到整个系统;待到代码质量已经堕落到不可修复的地步时,再要挽回,可能已经覆水难收了。

相关文章
|
7月前
|
供应链 安全 前端开发
【开题报告】基于JavaWeb的有机蔬菜销售系统的设计与实现
【开题报告】基于JavaWeb的有机蔬菜销售系统的设计与实现
150 0
|
1月前
|
关系型数据库 程序员 BI
程序员中医诊所管理软件开发模块分析
将系统划分为患者管理模块、医生管理模块、药材药方管理模块、财务管理模块、报表统计模块和系统设置模块。
26 2
|
1月前
|
监控 前端开发 Java
Java公立医院绩效考核管理系统 医院绩效考核系统的优势有哪些? 
医院绩效管理系统解决方案紧扣新医改形势下医院绩效管理的要求,以“工作量为基础的考核方案”为核心思想,结合患者满意度、服务质量、技术难度、工作效率、医德医风等管理发展目标的考核体系,形成医院的内部绩效考核与分配机制,通过信息化手段为绩效考评管理人员实施医院绩效考评工作提供了有效工具,扩展了信息管理范围,增加了信息分析的广度与深度。这不仅使绩效评价工作更加科学化、规范化和自动化,而且从根本上改变了绩效评估工作方式,实现了绩效评价数据网络化采集,评价结果透明化管理,奖金分配数据自动化生成,极大地提高了绩效评估的全面性、准确性、时效性、公正性。从而推进医院绩效管理的专业化、规范化和精细化管理,充分发挥
23 0
|
1月前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码B/S架构+springboot三级公立医院绩效考核系统源码 医院综合绩效核算系统源码
作为医院用综合绩效核算系统,系统需要和his系统进行对接,按照设定周期,从his系统获取医院科室和医生、护士、其他人员工作量,对没有录入信息化系统的工作量,绩效考核系统设有手工录入功能(可以批量导入),对获取的数据系统按照设定的公式进行汇算,且设置审核机制,可以退回修正,系统功能强大,完全模拟医院实际绩效核算过程,且每步核算都可以进行调整和参数设置,能适应医院多种绩效核算方式。
40 2
|
7月前
|
Java 关系型数据库 MySQL
基于SSM的流浪动物救助及领养管理系统(有报告)。Javaee项目。
基于SSM的流浪动物救助及领养管理系统(有报告)。Javaee项目。
|
1月前
|
传感器 数据采集 安全
C#智慧医院手麻系统源码 医院手术麻醉系统源码 支持三甲医院评级需求 可提供演示
手术麻醉管理系统是应用于医院手术室、麻醉科室的计算机软件系统。该系统针对整个围术期,对病人进行全程跟踪与信息管理,自动集成病人HIS、LIS、RIS、PACS信息,采集监护等设备数据,根据质控要求自动生成电子单据,把麻醉医生从繁重的单据记录中解放出来,从而有更多时间为手术顺利进行保驾护航。麻醉信息管理系统覆盖了从患者入院,经过术前、术中、术后,直至出院的全过程。通过与医院信息系统的信息集成,与监护设备的数据集成,实现了围术期患者信息的自动采集与共享,建立围术期电子病历
25 0
|
1月前
|
前端开发 Java 关系型数据库
Java医院绩效考核系统源码 三级公立医院绩效考核系统源码
开发工具:maven、Visual Studio Code 前端框架:avue 后端框架:springboot、mybaits
22 0
|
1月前
|
前端开发 JavaScript Java
灾区物资救助系统|基于Springboot开发实现灾区物资救助系统
灾区物资救助系统|基于Springboot开发实现灾区物资救助系统
|
1月前
|
运维 安全 数据库
PAM案例——某三甲医院
x医院现有业务系统100+,所对接的数据库已超300+,业务系统再持续扩大。针对目前数据库访问该医院数据中心技术人员是这样描述的:内部人员和第三方人员访问数据库的访问路径、访问方式难以管理,而且很难从根源消除这种现象,有时发生紧急情况他们会绕过审计系统直接访问数据库去修改数据,他们登录数据库后干了什么、改了什么、甚至删除了什么我们全都不知道。
34 0
PAM案例——某三甲医院
|
7月前
|
JavaScript 前端开发 Java
Java民营医院、门诊部、卫生院、连锁医院、公立医院云HIS信息管理系统源码
基于云计算的云医疗信息系统(云HIS)。以SaaS的方式提供服务,系统遵循服务化、模块化原则开发,具有强大的可扩展性,二次开发方便快捷。 系统采用前后端分离架构,前端由Angular语言、JavaScript开发;后端使用Java语言开发。融合B/S版电子病历系统,支持电子病历四级。系统运行稳定、功能齐全,界面布局合理、操作简便。
152 0

热门文章

最新文章

相关实验场景

更多