关于代码设计的几点思考

关注公众号:xy的技术圈

这篇文章主要总结一下自己在平时编码中遇到一些问题的思考。很多时候,代码设计很难十全十美,我们面对的往往是选择题,如何在多个选择之间权衡取舍,并不是一件简单的事情。

性能与可读性

“性能”是一个优秀的程序员都会注意的点。在写代码的时候,我们往往希望自己能够以最优的性能写出满足需求的程序。但是有时候,一味的高性能可能会降低代码的可读性。

比如在数据库字段设计的时候,有一个“年份”的字段,我们都知道年份一般都是4位数,于是我们使用了SMALLINT来作为这个字段的类型。使用ORM映射到Java,就是short类型了。

熟悉Java语言的同学应该知道,Java语言在设计数值的运算的时候,会隐式地把它转换为int类型,而int类型必须要显式地强转才能转成short,所以造成了在代码里有非常多的(short)强转,如下:

short year = repository.getYearById(id);
short nextYear = (short)(year + 1);
复制代码

这样的强转其实并没有多大的意义,降低的可读性的同时,并没有带来多大的性能提升。

所以我们在写程序的时候,有时候需要在追求性能和代码可读性方面权衡,在可接受的性能下,尽量让代码更加可读

通用性与过度设计

如果有多个地方用到相同的逻辑,我们可以考虑把它抽出去,封装成一个私有方法甚至是一个类。我们称这段抽取后的代码具有“通用性”,也称为“复用性”。

大家或多或少都了解过一些设计模式,事实上许多设计模式解决的问题正是代码的“复用性”。代码的复用性很重要,但有时候可能也会带来复杂性的上升。尤其是在抽取了一些额外的类之后,你的代码就会多出很多xxBuilder, xxFactory, xxProxy之类的。更有甚者,直接是xxUtils, xxHelper等不明职责的类,里面许多杂乱的方法。

公共的方法和类带来的另一个问题是,如果两个业务同时用了这一段代码,后来需求发生了更改,其中一个业务在这段代码里面的逻辑需要发生一些小的变化,那如果去改公共代码,就会影响另一个业务。

所以在“通用性”和“复杂性”之间怎么权衡?答案就是“简单设计”,不要“过度设计”。

在写代码的时候,尽量让代码简单,不为了复用性去抽取很多的方法和类,只为了可读性去做必要的重构。只有到了必要的时候才抽取公共的方法或类,比如多个业务确实用到了同一段复杂逻辑,且大概率不会发生更改。

代码是这样,接口亦是这样。不要因为考虑到“以后可能会用到”就去过度设计,到时候做到了那个功能再重构不迟。

至于具体怎样去实施?我认为没有一个明确的准则,需要多写代码,多思考,多了解业务,才能敏锐地判断是否需要抽取。

写多少测试?

笔者平时项目上都是遵循的TDD流程,所以写的测试还算比较多。在我们项目上,尽量是优先写单元测试,然后集成测试只写必要的happy-path,有些比较简单的类甚至可以不写集成测试,比如controller层。先写测试,再写实现,测试驱动出实现代码。

《重构》这本书里面讲到,写测试不应该是一个固定的,死板的开发流程,我们写测试的时候,应该只写必要的测试,只写那些我们没有十足的信心的代码的测试。

但这似乎与TDD的“测试先行”的观点有些相违背。如果我们先写测试,那怎么能知道我们的实现代码长怎样呢,又怎么能知道我们有没有十足的信心呢?

其实在实际的项目开发中,测试想要100%覆盖是十分困难的。尤其是在“白名单”这种情况下,我们往往只能测出白名单内的参数是符合条件的,但不可能测完所有的白名单外的参数是不符合条件的。所以只能选一两个去测试。

有时候想要先写测试是十分困难的。比如我们在实现的时候可能会依赖其它的类,所以我们在测试的时候可能会去mock这个类,但在写实现代码的时候,发现这个类不合适,可能需要换成调用另一个类。这个时候又回过头来改测试,就显得有些麻烦。所以有时候并不一定是完全的测试先行,而是形成了一个环,测试-实现-重构。

总结

上面讲到的是笔者对日常开发中遇到的一些问题的思考。其实有时候也得要根据实际情况去考虑,在写代码的时候,需要考虑的东西很多,比如可读性、可维护性、可扩展性、性能、测试等等。

编程是一项工程,如何把这件工程做好,就需要我们掌握更多的知识。多研究一下别人是怎么写代码的,它们有什么优劣,看得多了,总结得多了,“经验”自然就有了。

推荐书籍:

  • 《重构》
  • 《代码整洁之道》
  • 《Effective Java》
  • 《设计模式》
  • 《实现领域驱动设计》

认真写文章,用心做分享。

个人网站:yasinshaw.com

公众号:xy的技术圈