[面试][设计模式]Code Review 会注意哪些事?会依照什麽原则对程序做 Refactoring?

在功能稳定後,你对程序码还有要求吗?

没有最好的程序,只有更好的程序。」在完成功能後 Code Review 是非常重要的事情;不只看自己的程序码,也要多观摩其他人的程序码,这个动作除了可以优化程序码品质外,还可以帮助团队了解彼此的工作进度

人无完人,很多时候第一时间想到的解法未必是最好的;解决问题的方法不只一种,可能有更简洁的写法或是更好的 Pattern 可以套用,只是开发者不具备这块的知识储备;此时 Code Review 就是提升团队程序水平的方式。

找到需要改善的问题後,Refactoring(重构)就是另一个开始;如果为了尽快完成功能而埋头狂写,容易导致日後维护以及交接的困难性;专案越大这个问题会越明显,所以在完成功能後对程序做 Refactoring 是必要的任务,它能提升你日後解决 Bug、需求变更时的工作效率

大纲

  1. Code Review 会注意哪些事?会依照什麽原则对程序做 Refactoring?

    • 1.1 面试官为什麽会问?
    • 1.2 面试官想从答案确认什麽?
    • 1.3 笔者提供的简答
  2. 回答问题所需具备的知识

    • 2.1 SOLID 原则
    • 2.2 Design Pattern 是什麽?解决了什麽问题?
  3. 衍伸问题

    • 3.1 有处理 Legacy Code 的经验吗?
    • 3.2 有用什麽工具让团队有一致的 Coding Style

1. Code Review 会注意哪些事?会依照什麽原则对程序做 Refactoring?

1.1 面试官为什麽会问?

只要是软件工程师的职位,这题可以说是超级常见面试题;因为它能问得很浅也能够问得很深,如果是相对资浅的职位,求职者只要能说出关键字并给出基本解释就能过关;如果是资深的位置,除了关键字外,面试官会继续询问你在专案中是否有实践


1.2 面试官想从答案确认什麽?

  • 了解求职者 Code Review 会在意的点
  • 有使用哪些方法对程序做 Refactoring
  • 对 SOLID 原则有基础认知
  • 知道 Design Pattern 且能举出实务上的应用
  • 有用过什麽工具让团队有一致的 Coding Style

1.3 笔者提供的简答

我会先观察变数与函式的命名是否符合团队规范、变数的宣告是否合理(var/let/const 的使用时机);然後看程序在设计上是否有符合 SOLID 原则;在功能完成後如果发现有合适的 Pattern 可以更好地解决问题,就会再对程序做 Refactoring。


2. 回答问题所需具备的知识

其他章节的知识,可能换一个时空背景就用不到了;但今天这个章节的内容,是每位开发人员都需要将其内化成为本能的

2.1 SOLID 原则

SOLID 是一个物件导向的设计原则,核心目标是为了让程序码更乾净、易读、好维护,让程序朝着 Clean Code(无瑕的程序码)的方向前进。

  • S(Single Responsibility Principle)单一责任原则
    一次只做好一件事;如果把系统比喻成乐高,那单一责任原则就是乐高的每一块积木。
  • O(Open-Closed Principle)开放封闭原则
    程序的架构要容易扩充功能;而这个功能的扩充是新增程序,而非修改过去的程序,这麽做可以维持原有程序的稳定性。
  • L(Liskov Substitution Principle)里氏替换原则
    在继承中衍生的子类别,要能完全继承父类别的功能;实作时还需顾虑到单一责任原则,避免日後功能拆分或删减时的困扰
    以软件的角度来说,使用者期待版本从 1.0 ➞ 1.1 时行为是一致的,不会更新完旧功能就坏掉

    子类别 Override 时要注意

    • 不可改变父类别先决条件
      假设篮球比赛的参与者年龄限制在 15~80 岁;里面可能区分为青年组、中年组、老年组,青年组的年龄「可以是 15~30」;但「不可以是 14~30」。
    • 子类别的後置条件不该被削弱
      篮球的得分有「2」分球、「3」分球,都是数字;如果某个比赛改用「三分球」这个字串纪录得分就是不可以的。
  • I(Interface Segregation Principles)介面隔离原则
    设计程序时不应该预设使用者一定会使用到介面中的多个功能,我们应该尽量让介面的功能单一;以後端 API 来举例,我们不要让一个 API 可以做好多事情,但做每件事情时只用到其中几个参数
  • D(Dependency Inversion Principle)依赖反转原则
    子类别一定会依赖父类别,这样单向的关系是乾净的;如果是双向的依赖就会导致程序码逻辑难以追踪。

2.2 Design Pattern 是什麽?解决了什麽问题?

Pattern 是指在不同场景下的解释,包含 Context(情境叙述)、Problem(问题)、Solution(解决方案)。

Design Pattern 就是过去人们发现解决问题的套路;学习越多 Design Pattern 在遇到问题时可以有更多的思路,并缩短与其他工程师讨论的时间,因爲 Pattern 本身就包含了对问题的基础解决方案,可以省下说明的时间。

下面分享一些常见的 Design Pattern 帮大家建立基础概念,在看完後读者也许会恍然大悟,原来我一直都有在使用 Pattern!

  • Factory Method 工厂方法
    简述工厂负责生产客户需要的产品
    假设今天有一个饮料工厂,他会生产很多种类的产品,像是「红茶、绿茶、乌龙茶」;也许这些饮料的制作过程很复杂,但对客户来讲只要说出自己想要什麽口味的饮料就好了。

  • Strategy 策略
    简述一个策略介面底下有很多灵活的方法供选择
    把计算机的计算当成一个介面,这个介面提供加减乘除的方法;如果想加入次方、开根号的功能也能够轻松扩充。

  • Factory Method 与 Strategy 混合运用
    简述有时解决方案是多个 Pattern 的组合
    假设今天开一间饮料店,饮料会分成茶类、咖啡;每杯饮料又有冰量、甜度的选择,这里我们可以用 Factory Method 来建立产品;而饮料在贩售时,不同组合有对应的折扣,这里就可以用 Strategy 提供对应的方法来计算总金额。

  • Singleton 单例
    简述希望在程序每个位置都能呼叫统一的 Instance
    在写程序时会希望某些资源可以重复利用,并且在不同档案引用时这些资料是一致的,像是共用的计时器、资料库等物件。

  • Decorator 装饰
    简述将需求独立,依据实际需求取得动态的结果
    假设一间餐厅提供三种餐点

    • 商业午餐(主餐)
    • 简餐(主餐 + 饮料)
    • 套餐(主餐 + 饮料 + 甜点)

    我们发现上面餐点是有叠加性的,所以可以用继承的方式来撰写餐点间的关系;但如果今天老板想要更换简餐的内容组合(ex:赠送水果),而不想改变套餐的内容,就会导致程序必须改写。
    面对这个问题,我们可以用 Decorator 作为解决方案,将主餐、饮料、甜点、水果各自独立,依据商业午餐、简餐、套餐实际的内容来做组合;这样就能减少耦合性的问题。

  • Observer 观察者
    简述观察者要能掌握被观察者的状态改变,较常用於 GUI 的设计
    像是 Vue.js 的 watch 就是用来监听一个「值」的变化去做一系列的事情(ex:UI 上的调整)。

  • Command 命令
    简述将请求的物件和执行的物件分开
    路边摊的老板通常是一个人负责备料、点餐、料理、清洁…等到生意做起来有店面了,他才会考虑雇用一些人手来帮忙内场、外场,如果把这个解决方案用 Command 来举例:

    • 客人(Client):跟服务生说自己想点什麽餐(Concrete Command)。
    • 服务生(Invoker):纪录客人的餐点(Concrete Command),并呼叫命令。
    • 厨师(Receiver):收到命令後,开始料理餐点(Concrete Command)。
  • Builder 建造者
    简述依照自己的需求客制化产品
    一台笔电是由许多零件组装而成,你可以选择标配也能够用选配来客制化它;为了让选配的零件规格有弹性,Builder 就是一个很棒的解决方案;它让你先选完自己所需的零件後,再产生最後的产品。

备注:如果你熟悉的 Pattern 刚好能解决目前遇到的问题那是皆大欢喜;万一没有合适的 Pattern 可以套用,千万不要为了设计而设计;开发人员的目标应该是想透过 Pattern 解决问题,而不是从 Pattern 出发去设计问题。


3. 衍伸问题

3.1 有处理 Legacy Code 的经验吗?

除非是新创团队或是接案类型的公司;不然绝大多数新人入职後都要接手前人遗留的程序码(Legacy Code),如果求职者只有从零开始的经验,在一开始会有阵痛期。

考点:了解求职者是否接触过 Legacy Code、碰到时如何快速掌握

之前有因为专案扩编而去支援的经验,因为当时有时程上的压力;所以我在加入团队後除了重点了解自己要负责的 Feature、团队的 Coding Style 外,会主动向定下这些需求的 PM 讨论,以此了解因果关系并快速融入团队。

也曾接手过要维护及扩充的专案,因为当时没有相关文件可以参考,为了快速掌握专案,我一边操作系统一边画心智图帮助自己厘清功能;了解系统架构後再去找合作窗口确认需求,避免实作方向与客户需求不同。

在修改 Legacy Code 时,我会特别注意前人是否有撰写测试案例,在程序能有效测试的状态下才能安心的修改与扩充。

以正在运行的专案来说,我会希望建立团队 Code Review 机制,让每个人熟悉彼此的程序、使用共同的 Coding Style,当知识共享与风格统一後,就能减少 Legacy Code 的产生


3.2 有用什麽工具让团队有一致的 Coding Style

考点:是否有善用工具解决问题

我在 VSCode 有安装一个 Prettier 的外挂,靠它就能非常有效的将程序码格式化;不过有时为了保证团队的 Coding Style 会导入 ESLint 来统一风格,但 Prettier 与 ESLint 一起使用时会遇到冲突,所以我有再安装一个 Prettier ESLint 的外挂来解决这个问题;让程序不仅符合团队的 Coding Style,还可以透过快速键将程序码格式化。


写完这篇文章时,我的心里突然冒出一段话:「一开始我们在追求问题的解答;到後来,我们在学习解决问题的方法。」

感谢大家的阅读,如果喜欢我的文章可以订阅接收通知;如果有帮助到你,按Like可以让我更有写文的动力,我们明天见~

参考资源

  1. 【翻译】JavaScript 设计模式
  2. 什麽是 Design Pattern?
  3. 我为什麽想学设计模式 ( Design Pattern )
  4. 设计模式五大基本原则 SOLID

我在 Medium 平台 也分享了许多技术文章
❝ 主题涵盖「MIS & DEVOPS资料库前端後端MICROSFT 365GOOGLE 云端应用自我修炼」希望可以帮助遇到相同问题、想自我成长的人。❞


<<:  # Day21--Git标准姿势?基本动作?

>>:  D28 - 压测

离职倒数26天:快乐生活指南

睡觉、运动、读书、写日记、呼吸冥想、跟久未联络的朋友联系,这些常被推崇的快乐生活指南,我这个月都实践...

第23车厢-在网页中预览pdf—pdf.js简易版应用篇

本篇介绍JS插件pdf.js简易应用篇 今天介绍透过插件pdf.js在网页中直接浏览pdf 官网:...

DAY 12:Concurrency Patterns 融会贯通+Graceful Shutdown,正确关闭各个宇宙的次元门

前 11 天已经将常见的 concurrency patterns 介绍完毕,今天我们要介绍的不是 ...

完赛心得

首先很感谢it铁人赛给予我这个机会,让我做到平时连想不会想到的事。因为对於我而言,实在是没什麽能够说...

Day 14 读 Go Concurrency Patterns - Rob Pike I

本篇是看 Go Concurrency Patterns 的心得 简报网址:https://talk...