code smells可以理解为代码中让人感觉到不舒服的地方。可能是代码规范问题,也可能是设计上的缺陷。
很多时候一段代码符合基本逻辑,能够正常运行,并不代表它是不“丑”的。代码中可能会存在诸如可读性差、结构混乱、重复代码太多、不够健壮等问题。
示例代码
上述代码实现了一个简单的“员工管理系统”。
Employee 类代表公司里的员工,有姓名、角色、假期等属性。可以请假(),或者单独请一天,或者以 5 天为单位将假期兑换为报酬
HourlyEmployee 和 MonthlyEmployee 分别代表以时薪或者月薪来计算工资的员工
Company 类代表公司,可以招收员工()、返回特定角色的员工列表(如 )、发放薪资等()
code smells
上面的代码中存在着很多可以改进的地方。
用 Enum 类型替代 str 作为员工的 role 属性
上面的 类使用了 类型来存储 属性的值,比如用 代表经理,用 代表实习生。
实际上 String 过于灵活,可以拥有任何含义,用来表示角色属性时不具有足够清晰的指向性。不同的拼写规则和大小写习惯都会导致出现错误的指向,比如 和 , 和 。可以使用 Enum 替代 str。
修改 类中 属性的定义:
类中 等方法也做相应的修改:
方法中使用新的 role 创建员工对象:
消除重复代码
类中有一个功能是返回特定角色的员工列表,即 、、 三个方法。
这三个方法实际上有着同样的逻辑,却分散在了三个不同的函数里。可以合并成一个方法来消除重复代码。
同时将 函数中的 、、 都改为如下形式:
尽量使用内置函数
上面版本中的 方法,包含了一个 循环。实际上该部分逻辑可以使用 Python 内置的列表推导来实现。
合理的使用 Python 内置函数可以使代码更短、更直观,同时内置函数针对很多场景在性能上也做了一定的优化。
更清晰明确的变量名
旧版本:
新版本:
isinstance
当你在代码的任何地方看到 这个函数时,都需要特别地加以关注。它意味着代码中有可能存在某些有待提升的设计。
比如代码中的 函数:
这里 的使用,实际上在 函数中引入了对 的子类的依赖。这种依赖导致各部分代码之间的职责划分不够清晰,耦合性变强。
方法需要与 的子类的具体实现保持同步。每新增一个新的员工类型( 的子类),此方法中的 也就必须再新增一个分支。即需要同时改动不同位置的两部分代码。
可以将 的实现从 类转移到具体的 子类中。即特定类型的员工拥有对应的报酬支付方法,公司在发薪时只需要调用对应员工的 方法,无需实现自己的 方法。由 引入的依赖关系从而被移除。
再把 函数中的 改为 。
由于每一个特定的 子类都需要实现 方法,更好的方式是将 实现为虚拟基类, 成为子类必须实现的虚拟方法。
Bool flag
类中的 方法有一个名为 的参数。它是布尔类型,作为一个开关,来决定某个员工是请一天假,还是以 5 天为单位将假期兑换为报酬。
这个开关实际上导致了 方法包含了两种不同的职责,只通过一个布尔值来决定具体执行哪一个。
函数原本的目的就是职责的分离。使得同一个代码块中不会包含过多不同类型的任务。
因此 方法最好分割成两个不同的方法,分别应对不同的休假方式。
Exceptions
方法中有一步 代码。但该部分代码实际上对 Exception 没有做任何事。对于 Exception 而言:
如果需要 catch Exception,就 catch 特定类型的某个 Exception,并对其进行处理;如果不会对该 Exception 做任何处理,就不要 catch 它。
在此处使用 会阻止异常向外抛出,导致外部代码在调用 时获取不到异常信息。此外,使用 而不是某个特定类型的异常,会导致所有的异常信息都被屏蔽掉,包括语法错误、键盘中断等。
因此,去掉上述代码中的 。
使用自定义 Exception 替代 ValueError
是 Python 内置的在内部出现值错误时抛出的异常,并不适合用在自定义的场景中。最好在代码中定义自己的异常类型。
最终版本
参考资料
7 Python Code Smells: Olfactory Offenses To Avoid At All Costs
来源:
https://www.starky.ltd/2021/12/06/7-python-code-smells-by-practical-example/