Clojure公报的最新一期一直在讨论抽象,这让我思考。
我对不必要的抽象保持警惕,并看到了许多代码库,其中一些是我贡献的代码库,本质上是半支持抽象的盲目混乱。
这就是我在想...
抽象有半衰期
抽象具有半衰期,也就是说,有时隐含在该抽象中的确切语句不再完全有效。 当抽象为您的业务领域的某个方面建模时,尤其如此。 随着时间的流逝,您对业务的了解会增加,或者新的要求会逐渐暴露出来,您所做的某些假设可能不再成立。
为了使抽象适应新知识,我们进行重构。 如果抽象被很好地隔离,那么重构就很简单。 但是,通常将抽象嵌入到设计中,并且更改会在我们的代码库中引起连锁反应,并且突然之间,我们将对新的抽象执行“开放式心脏手术”或“增量更改”。 在此过程中,我们处于无效状态。 通常,我们没有完全完成此重构工作,最后得到了半途而废。 有了足够的这些中途之家,我们最终会陷入混乱,开始梦想重新编写系统。
抽象意味着更多代码?
这不是很明显吗? 当然没有抽象意味着最少的代码对吗? 抽象是我所谓的“组织”代码,而不是“正在执行”的代码。 如果帮助正确,它们将帮助我们理解问题和解决方案。 它们隔离变更并有助于组合。
可组合性很有趣。 它实际上允许我们在多个地方重用相同的抽象,从而帮助我们减少了代码。 但是,抽象越复杂,它就越具体,因此不容易重用。 还有另一个怪物。 重复使用抽象是件好事,但是如果这种抽象很可能经常改变,那么我们已经在一些不稳定的基础上建立了自己的房子。
那么对于本质上更通用的较小抽象是否存在这种情况? 在我看来,较小的抽象是理所当然的。 SOLID中的 “单一责任原则”和简单设计的4个元素中的更少元素就指出了这一点。 如果抽象很小且仅用于一个目的,则它们的有效期更长。
但是“自然界通用”呢?
并非所有抽象都相等
本质上更通用的抽象具有更长的半衰期。 编程语言本身是具有很长半衰期的抽象。 它非常通用,不会对特定问题域做出很多明确的陈述。 数据访问库是另一种抽象,位于上面几个级别,并且在系统中的半衰期更短–该库可能旨在访问关系存储,并且如果您要去其他存储,则可能需要替换该库。
假设越少,其中之一可能变为无效的可能性就越小。 例如,与仅对列表的特定实现进行操作的功能相比,对通用集合进行操作的功能不太可能需要更改。
有关业务领域的抽象仍然更高,并且可能会更频繁地更改。 同样,业务的某些方面将比其他方面更加不稳定。
更多易失的抽象不应深入地嵌入到您的代码中,也不应具有复杂的依赖关系图,以便在需要时将更改本地化。
抽象有导航开销
抽象可以帮助或阻碍理解。 以编程语言为例进行抽象。 我们大多数人甚至都不了解编程语言的下一级细节(即字节/机器码)。 可以说,这是一个展示意图的抽象集的很好的例子。 如此有效,以至于我们很少需要更深入地理解。 尽管可以说出一种真正的意图来揭示编程语言并不需要所有相关的文档……但是我离题了。
如果理解需要从抽象的一个层次挖掘到下面的层次,那么抽象实际上就成为障碍,因为每次跳转都需要读者牢记先前的层次。 例如,我经常看到“提取方法/类/模块”的自由使用(可能被称为“纯净代码”),以至于我发现自己不断地跳入和跳出不同的抽象级别,以便理解简单的执行流程。 组合方法很棒,但同时大量使用方法/类/模块提取而又不密切关注每种抽象的优缺点并不是那么聪明。
抽象经销商
处理抽象是我们的工作,但我们必须注意,这并非没有代价。 创建一个抽象不应掉以轻心–我们必须仔细考虑它们,并试图了解它们的成本和收益。 犯错是不可避免的,但是我们应该增加抽象的半衰期,并使用它们来帮助而不是阻碍理解和局部化变更。
不稳定的抽象并非总是可以避免的。 但是,如果我们知道抽象是易变的,那么我们必须设法隔离影响,以防需要更改。
翻译自: https://www.javacodegeeks.com/2016/06/thinking-in-abstractions.html