它闻起来很臭,因为可能有很多情况可以对其进行编辑或改进。
大多数这些气味只是暗示可能有问题。它们本身不需要固定……(不过你应该研究一下。)
让我们继续...
不要添加 IF 检查生产环境。
TL;DR:避免添加与生产相关的条件
有时,我们需要在开发和生产中创建不同的行为。
例如密码的强度。
在这种情况下,我们需要使用强度策略配置环境并测试策略而不是环境本身。
def send_welcome_email(email_address, environment): if ENVIRONMENT_NAME == "production": print(f"Sending welcome email to {email_address} from Bob Builder <[email protected]>") else: print("Emails are sent only on production") send_welcome_email("[email protected]", "development") # Emails are sent only on production send_welcome_email("[email protected]", "production") # Sending welcome email to [email protected] from Bob Builder <[email protected]>
class ProductionEnvironment: FROM_EMAIL = "Bob Builder <[email protected]>" class DevelopmentEnvironment: FROM_EMAIL = "Bob Builder Development <[email protected]>" # We can unit test environments # and even implement different sending mechanisms def send_welcome_email(email_address, environment): print(f"Sending welcome email to {email_address} from {environment.FROM_EMAIL}") # We can delegate into a fake sender (and possible logger) # and unit test it send_welcome_email("[email protected]", DevelopmentEnvironment()) # Sending welcome email to [email protected] from Bob Builder Development <[email protected]> send_welcome_email("[email protected]", ProductionEnvironment()) # Sending welcome email to [email protected] from Bob Builder <[email protected]>
这是一种设计气味。
我们需要创建空的开发/生产配置并使用可定制的多态对象来委托它们。
避免添加不可测试的条件。
创建委派业务规则的配置。
使用抽象、协议和接口,避免硬层次结构。
这条推文的灵感来自@Jan Giacomelli
复杂性是技术不成熟的标志。无论是 ATM 还是爱国者导弹,使用简单是设计精良产品的真正标志。
丹尼尔·T·灵
重用变量使范围和边界更难遵循
TL;DR:不要为了不同的目的读写同一个变量
在编写脚本时,重用变量是很常见的。
这会导致混乱并使调试更加困难。
我们应该尽可能地缩小范围。
// print line total double total = item.getPrice() * item.getQuantity(); System.out.println("Line total: " + total ); // print amount total total = order.getTotal() - order.getDiscount(); System.out.println( "Amount due: " + total ); // variable is reused
function printLineTotal() { double total = item.getPrice() * item.getQuantity(); System.out.println("Line total: " + total ); } function printAmountTotal() { double total = order.getTotal() - order.getDiscount(); System.out.println( "Amount due: " + total ); }
Linter 可以使用解析树来查找变量定义和用法。
避免重复使用变量名。使用更具体和不同的名称。
先简单后通用,先用后重用。
凯夫林·亨尼
断言两个浮点数相同是一个非常困难的问题
TL;DR:不要比较浮点数
比较浮点数是一个古老的计算机科学问题。
通常的解决方案是使用阈值比较。
我们建议完全避免使用浮点数并尝试使用无限精度数。
Assert.assertEquals(0.0012f, 0.0012f); // Deprecated Assert.assertTrue(0.0012f == 0.0012f); // Not JUnit - Smell
Assert.assertEquals(0.0012f, 0.0014f, 0.0002); // true Assert.assertEquals(0.0012f, 0.0014f, 0.0001); // false // last parameter is the delta threshold Assert.assertEquals(12 / 10000, 12 / 10000); // true Assert.assertEquals(12 / 10000, 14 / 10000); // false
我们可以在我们的测试框架上添加一个检查 con assertEquals()以避免检查浮点数。
我们应该始终避免比较浮点数。
照片由Mika Baumeister在Unsplash上拍摄
上帝创造了自然数;其他一切都是人的工作。
利奥波德·克罗内克
如果结合 4 种代码气味会发生什么?
TL;DR:避免 Getter,避免 Setter,避免元编程。想想行为。
Setter 和 getter 是一种不好的行业惯例。
许多 IDE 都喜欢这种代码味道。
一些语言为构建贫血模型和 DTO 提供了明确的支持。
class Person { public string name { get; set; } }
class Person { private string name public Person(string personName) { name = personName; //imutable //no getters, no setters } //... more protocol, probably accessing private variable name }
这是一个语言特性。
我们应该避免不成熟的语言或禁止他们最糟糕的做法。
在暴露我们的属性之前,我们需要仔细考虑。
第一步是停止思考属性,只关注行为。
没有什么比在紧迫的期限内工作并仍然花时间清理工作更难的了。
肯特贝克
默认意味着“我们还不知道的一切”。我们无法预见未来。
TL;DR:不要在您的案例中添加默认条款。将其更改为异常。明确。
在使用案例时,我们通常会添加一个默认案例,这样它就不会失败。
失败总是比在没有证据的情况下做出决定要好。
由于外壳和开关也是一种气味,我们可以避免它们。
switch (value) { case value1: // if value1 matches the following will be executed.. doSomething(); break; case value2: // if value2 matches the following will be executed.. doSomethingElse(); break; default: // if value does not presently match the above values // or future values // the following will be executed doSomethingSpecial(); break; }
switch (value) { case value1: // if value1 matches the following will be executed.. doSomething(); break; case value2: // if value2 matches the following will be executed.. doSomethingElse(); break; case value3: case value4: // We currently know these options exist doSomethingSpecial(); break; default: // if value does not match the above values we need to take a decision throw new Exception('Unexpected case ' + value + ' we need to consider it'); break; }
我们可以告诉我们的 linter 警告我们默认使用,除非有例外。
编写健壮的代码并不意味着我们需要在没有证据的情况下做出决定。
Code Smell 36 - Switch/case/elseif/else/if 语句
照片由Joshua Woroniecki在Unsplash上拍摄
添加功能的成本不仅仅是编码所需的时间。成本还包括为未来扩展增加的障碍。诀窍是选择不相互冲突的特征。
约翰·卡马克
这就是现在……
下一篇文章将解释另外 5 种代码异味!