如何在没有更多抽象的情况下获得最佳可读性
我以各种方式重构了一个复杂的“ if”语句。 我发现了一种有趣的趋势。 使用简单的重构步骤,在追求更高的代码可读性时,您通常可以朝着不同的方向前进。 每一次小的重构都是可读性的增加,有时是复杂性或抽象的减少或增加,或者是中立的。
TL; DR:从这个实验中,我了解到在重构时我们不应太急于添加新的抽象级别。 也许有一种方法可以在当前抽象的范围内提高可读性。
而且您不希望代码看起来像这样:
代码示例
让我举一个例子。 我对“剪刀石头布”编码Kata进行了以下实现:
第一条重构路径
我的第一个直觉是,这些“ if”语句正在验证第一罚是否胜过第二罚。 因此,我认为可以改善可读性的不错的抽象方法是:
但这只是一个问题。 “ beats”方法中的代码仍然不能很好地表达意图。 您几乎必须执行大脑中的代码才能了解其与业务规则的关系:
而且我们已经介绍了一种抽象级别。 条件的可读性没有提高-只是四处移动。 “播放”功能的可读性仅略有增加。
我的直觉为“节拍”中的可读性问题提出了一个立即解决方案:将枚举类转换为接口,并为每种类型的throw提供三个具体的实现:
现在,很明显,否定条件(就像other != ROCK
)不是我们真正需要谈论的。 取而代之的是,我们需要谈谈当前的一个拍子是哪个“其他投掷”:
通过使用字段“ winsAgainst”实例化SCISSORS,ROCK和PAPER并将其用于“ beats”方法的默认实现中,此重构路径可以走得更远:
因此,最终,我们必须再增加两层抽象,以使“播放”功能中的逻辑更具可读性,并使“ if”条件更好地表达意图。
众所周知,更多的抽象意味着更容易孤立地理解每一点,但更难于整体地理解它。
抽象泄漏。
这种抽象如何具有新功能?
让我们看看我们当前的抽象是否可以。 让我们将经典的剪刀石头布问题转变为更具文化现代感的剪刀石头布蜥蜴派克。 以下是规则列表:
(顺便说一句,这实际上是一个可运行的测试。感谢Kotlin的DSL优势!请参阅此处如何使用完整的TDD流编写此测试 。)
第一个断言错误是“ ROCK应该击败LIZARD”,但是如果我只替换“ winsAgainst”字段的值,它将失败,并显示“ ROCK应该击败SCISSORS”。
这意味着我们必须在Throw接口及其实现者之间中断API。 现在我们需要“ winsAgainst”作为列表:
尽管泄漏在一个文件中包含得很好,但抽象仍然泄漏。 如果将其与原始的“ if”语句进行比较,那么这种抽象的复杂性将使它非常烦人。
撤消! 并限制自己使用更少的抽象级别。
让我们回头,并尝试限制自己不要引入任何繁重的抽象。 我们可以使“播放”功能中的原始“ if”语句更具可读性吗?
解释变量
是。 解释变量。 让我们从“ second!= ROCK”之类的东西中删除该否定词,就像我们在其他重构路径中所学到的那样:
真可爱。 它更易于阅读,并且与业务规则更相似。
在这里,我有一个疯狂的问题:我可以在这里制作DSL,使代码看起来更接近于规则的人工描述吗?
疯狂的DSL
我可以! (不确定是否应该):
哇,这看起来很棒。 并读得很好。
除了“规则(第一,第二)”部分。 反正这如何工作?
“在这里,你让我迷路了,Oleksii!” —我现在要告诉自己。 Kotlin DSL魔术很多,例如infix扩展功能和lambda扩展功能。
但是,在那时,我专注于“ rules(…){…}”块的形状。 看起来有点熟悉。
DSL只是模式匹配。 简化!
模式匹配!
我在这里尝试将“第一和第二”对与某些组合(例如“剪刀和纸”,“纸和岩石”以及“摇滚和成功”)进行匹配。
这可以通过“ when”语句(Kotlin的经典“ switch”语句的高级版本)来完成:
现在,由于我们直接提到“ true”和“ false”,并稍后在“ if”语句中使用它,因此,我们通过删除布尔变量来简化“ play”功能:
此解决方案如何具有新功能?
因此,我们没有更多的抽象层(除了对Pair<A, B>
类的很少使用以及to
扩展功能的扩展)。 让我们来看看引入新规则有多么困难:
那很简单!
具有函数别名的更多表达式。 如果您对此感兴趣...
而且,如果您想使其更具表现力,则可以为infix函数“ to:” vs和beats(绝对可选)创建两个别名:
重构之旅的重点
重构时,我们的解决方案空间似乎是一个多维函数,我们正在尝试找到足够好的局部最大值。
就像教神经网络一样。 您的重构步骤是梯度下降。
现在,如果您仅探索单人后裔,您将可以正常使用。 但这实际上可能不是次优的局部最大值。 如果我们返回并尝试了几个不同的重构步骤(不一定更好),该怎么办?
好吧,事实证明,我的第一个重构路径生成了可读的代码,但是它引入了2个抽象级别(1个接口和少量实现)。
回头再尝试不同的选择时,例如尝试只引入1或0个最大抽象级别,它产生的代码虽然可读性强,但其中的抽象性却较低; 因此,更容易理解它的工作原理,并且抽象泄漏的机会更少。
我要说的是,我必须在几个第一步中使代码变得更糟,然后在接下来的步骤中迅速使其变得更加简单。
秘制酱
不要总是去进行初次感觉重构,探索替代方案,了解这种可读性,复杂性和抽象性多维功能的概况。
您应该考虑探索玩具代码而不是生产代码上的选项: 编码Katas就是完美的选择。 而且绝对可以退出重构路径并朝完全不同的方向移动。
使用生产代码,也可以。 你什么都没有浪费。 该代码是不值得的。 您获得并可以应用的知识就是金子,而且它永远存在。
确保检查出我的4部分,长达350页的电子书“ Ultimate Tutorial:Kotlin入门 ”。 除了Kotlin之外,它还充满了TDD,干净代码,软件体系结构,业务影响,5个为什么,接受标准,角色等优点。 下载并学习Kotlin 。
谢谢!
感谢您的阅读! 另外,如果您喜欢刚刚阅读的内容,请考虑给我鼓掌(中等程度(不超过50)),并在社交媒体上分享该文章。 那会让我超级开心! :)
From: https://hackernoon.com/how-to-get-to-the-best-readability-without-more-abstraction-4de9621f75c5