劣质代码产生的五个原因

来源:互联网 发布:网络负面的影响 编辑:程序博客网 时间:2024/05/22 08:25

此文来自:http://blog.submain.com/5-things-responsible-poor-code-quality/
这里写图片描述
5 Things Responsible for Your Poor Code Quality

July 25, 2017Erik DietrichArticles, CodeIt.Right, CodeQuality, ErikDietrich, LegacyCodeNo Comments
All right. Let’s get the obvious thing out of the way right up front.

One could easily argue that code quality (and poor code quality) is subjective. You use camel case while I use Pascal case, and we could argue about the “quality” of the code in those terms. But I have no interest in that.

So instead, let’s talk about code quality in terms of observable outcomes. You may look at a codebase and, based on your experience, conclude that this unfamiliar code is of poor quality. Who wrote this, anyway!? But you do that without understanding outcomes. Does this code serve its purpose in production with minimal issues? Does the application satisfy its constituents? And does the team consistently deliver features on time and on budget? At best, you can make educated guesses about that information when simply reading the code.

I have a bit of an advantage in my study of codebases. You see, I work as a consultant, specializing in codebase assessments, developer training, and team strategy. So I receive phone calls from clients when the external qualities are demonstrably poor — in other words, when the code has many issues, the application fails to satisfy its constituents, and the team misses deadlines and causes budget overruns. It turns out no prospective clients ever call to say, “Hey, things are GREAT, but will you come take a look at what we’re doing anyway?”

So I wind up looking at codebase after codebase that produces bad outcomes. I assess and analyze these things, and I’ve come away with some properties of the code that invariably correlate bad outcomes. Here are five things contributing to your poor code quality.

  1. Mutable Global State

Global state happens when you define variables with the scope of your entire application. That broad scope gives you the “global” part of the moniker, while the idea that you store information in them gives you the “state” part. When you make these variables mutable (able to change), you wind up with mutable global state.

You can find endless discussion about the merits of such a thing, and you’ll often hear global stated called “evil.” I’m going to stay strictly away from any theology or moralizing here and talk about mutable global state specifically in terms of effects. And global state has the effect of making it very hard to reason about your code.

To understand, consider a metaphor. Say that someone blindfolded you and asked you to reason about whether a light in a house was on based on the state of switches in the house. Is the bedroom light on? Well, is its switch in the on position? Then yes. What about the kitchen light? Ooh, that’s a bit trickier, since two switches control that light. But we can still map it out.

Now imagine a light for which every single person in your town had a switch. If you even bothered trying to reason about it, you’d give up quickly, and conclude that the only option is to look at the light. That describes global state. Anyone can change it from anywhere at any time for any reason. So if you want to reason about its value, you just have to run the code and see what happens.

And when developers give up trying to reason about and understand the code, that code’s quality suffers.

  1. Code Duplication

The second common theme of tortured codebases is code duplication. Sourcemaking categorizes this as a code smell, and you might better know it as “copy-paste programming,” or, colloquially, “copy pasta.” Whatever you call it, it amounts to taking the same code and reusing it by copying it wholesale and tweaking the results.

The simple act of doing this results in codebase noise. If you copy the same code 10 times instead of abstracting it to a single method that you call once, you have 10 times the code. And more code means more complexity and more things that can go wrong. Codebases that stand the test of time keep complexity to a bare minimum.

But beyond the noise problem, duplication has the effect of inviting errors and creating substantially more work down the line. It invites errors because it invites you to forget to tweak something that you need to tweak. And it creates downstream work because, now, when something about that code must change, you have to remember to make that change in 10 different places. What usually happens is that maintainers inevitably forget one or two places each time, and the duplicated code starts to drift apart with time, leaving 10 slightly different, buggy solutions to the same problem.

  1. Carelessness with Dependencies

One could argue that, at its center, software architecture is all about dependency management. I personally think of this as the core and most difficult problem in designing software. Dependency management factors into every class you write, every method you look at, and everything you do. If you look at the SOLID principles of OOP, you’ll see that all of them relate to dependency management, with two directly addressing it (the I and D).

Bad codebases wind up tied in knots. Modules depend on one another only because of some frivolous method call that’s not even used. You import an entire gigantic JavaScript framework just to perform an elementary calculation. Someone introduces a cyclical dependency with assemblies in the codebase.

Think of your code and architecture as a never-ending battle against a sort of entropy. By default and without deliberate intervention, your code tends toward spaghetti. So you see poor code quality in codebases where no one carefully considers dependencies.

  1. Opacity

When discussing code quality, you’ll typically hear people talk about readability. With poor quality code, people who are coming in unfamiliar and attempting to read it will struggle. Poor naming, strange formatting, and large units of code all contribute.

But I want to generalize this a bit and talk about opacity in code. Obviously, hard-to-read code is opaque. But you can also have other forms of opacity as well. Perhaps you have a poor abstraction that’s hard to reason about. Or maybe you have a class with an extensive call graph among its methods, making the path through that class’s logic opaque.

Generally speaking, you want code that clearly communicates both its purpose and intent. Opacity obscures this and leads to poor code quality.

  1. Lack of Automated Tests

Yes, you had to know this was coming. Not including it would be like a dentist not nagging you to floss. But I do this with a specific purpose.

Yes, automated tests can help you catch bugs and prevent regressions. And, yes, it’s generally a Good Thing™. But in my opinion, its biggest impact on code quality often goes unmentioned. A robust, well-maintained automated test suite gives you confidence while making changes to your code.

Now, confidence in changing your code does not directly translate to code quality. It does, however, make you much more likely to change your code, including to refactor. You can refactor your code to improve on all of the things mentioned here so far: moving away from global state, eliminating duplication, minimizing dependencies, and making your code clearer. When you make a point to refactor constantly, it means you make a point to clean and improve your code constantly.

Without an automated test suite, people tend not to have this confidence. Instead, they tend to treat the code as an ancient appliance — “it’s kind of working, so, whatever you do, don’t touch it.” And that’s essentially the hallmark of poor code quality.

Avoiding Poor Code Quality Is a Battle

When talking about code quality, it’s easy to seem (and be) judgmental. I spend a lot of time working with teams on lowering the total cost of ownership of codebases, so client developers often assume I’ll look judgmentally at what they’ve done.

But really, the more I do this, the less I judge. In the first place, I don’t know what sort of constraints they’ve faced and how the code got to the state it did. What’s more important is something that I actually explain to them when I arrive. Codebases naturally drift toward messiness unless you put forth a serious effort to stop that from happening. Poor code quality is the default state. So it’s not so much that they wrote poor quality code but rather that, for whatever reason, they didn’t have the time and resources to stop it from rotting.

Adopting that outlook is healthy for teams and for managers. Unless you invest in your team, your education, and your development process, your code will get more expensive to maintain as you go, and quality will suffer. So make the investment and avoid the sorts of traps I’ve discussed here.

以下为翻译内容

人们可能会认为判断代码质量的标准是主观的。当我使用Pascal变量命名法时,你却使用驼峰变量命名法,有人会用这些术语来谈论代码的“质量”,但是我并不想这么做。

所以,让我们来根据可观察到的结果来谈谈代码质量吧。在阅读代码的时候,你可能会根据经验来判断某个陌生的代码质量很差。这是谁写的代码?!你并没有去详细地了解一些情况就非常武断地就做出了这个判断。这段代码在生产中出的问题是最少的吗?应用程序的功能是否满足需求?项目团队是否能够按时按预算持续地进行交付?最好的情况就是,在你阅读代码的过程中得到这些问题的答案。

我个人在学习代码方面有一定的优势。因为我是一个顾问,专门从事代码评估、开发人员培训和团队战略方面的工作。所以,当客户的代码存在很多问题、应用程序不能满足要求、项目超期或者预算超支的时候,我就会接到来自客户的求助电话。事实证明,没有哪个客户会打电话来说:“嘿,我这里一切都很好,但你能来看看我们到底做得怎么样吗?”

所以,对于已经有了坏味道的代码,我不会再去阅读。对此我进行了评估和分析,发现某些代码总是与坏味道有关。下面是我总结出来的导致代码质量变差的五个因素。

1. 可变的全局状态

在你定义了一个作用域是整个应用程序范围的变量时,就会产生全局状态。整个应用程序的作用域范围被称为“全局”,而在其中存储的信息就是“状态”。当这些变量发生变化时,就产生了“可变的全局状态”。

对于这个的优缺点,人们可以没日没夜讨论个不停,你经常会听到有人把全局状态称为“邪恶的化身”。在这里我不会涉及神学或道德,而是从使用效果来谈论可变全局状态。全局状态会使你的代码难以理解。

我来打个比方:假设有人蒙上你的眼睛,然后要求你根据开关的状态来推断房子里的灯是否亮着。卧室是否亮着?它的开关是在“开”那个位置吗?是的。那么厨房里的灯呢?噢,这有点棘手,因为有两个开关可以控制那个灯。但我们仍然可以推断出灯的状态。

现在想象一下,假使城里的每一个人都有一个控制灯的开关。如果你试图根据开关的状态来推断灯是否亮着,那你会很快就会放弃,因为最好的办法就是直接观察灯的状态。这个例子很形象地描述了什么是全局状态。任何人都可以随时随地改变它。所以如果你想推断它的值,则你需要运行代码,看看会发生什么。

2. 代码重复

受代码折磨的第二个因素是代码重复。你可能会把这个称为“复制粘贴式编程”。它就是通过简单地复制粘贴代码块来实现同一个功能,达到重用代码的目的。

这种行为会给代码库带来噪声。如果你将相同的代码复制10次,而不是将其抽象出来以供调用,那么就会产生10倍的代码量。代码越多,意味着复杂性越高,出错的几率也越大。要让代码经受得住时间的考验,就需要尽可能地降低代码的复杂度。

除了噪声问题之外,重复的代码也会引入错误,并带来更多的工作。它会引入错误,因为它会让你忘记调整你需要调整的东西。它会带来更多的工作,因为当这段代码需要修改时,你必须记住在10个不同的地方同步做出修改。但是,维护者们经常会忘记一两个地方,并且这些重复的代码会随着时间的推移而发生变化,对于相同的问题留下了10个并不完全相同的解决方案。

3. 无人关心的依赖关系

有一种说法,软件架构的核心是对依赖的管理。我个人认为这是软件设计的核心以及最困难的问题。依赖管理存在于你写的每一个类、每一个函数,以及你所做的一切。如果你看一下OOP的SOLID原则,你会发现这所有的一切都与依赖管理相关,而且有两个直接与之对应(分别是I和D)。

糟糕的代码杂糅在一起。模块之间互相依赖,仅仅是因为一些无聊的方法调用。你导入整个的JavaScript框架,只是为了执行最基本的计算。有人甚至会在代码中引入与程序集的循环依赖。

请把代码和架构看作是对抗某一种熵的永无休止的战斗。默认情况下,如果没有特意地进行干预,代码就会变成意大利面条。所以,在无人关心依赖关系的代码中,你会发现代码质量很差。

4. 不透明

在讨论代码质量时,通常会听到有人谈论代码的可读性。对于质量差的代码,那些不熟悉并试图阅读它的人简直就是在煎熬。糟糕的命名、奇怪的格式和大量的代码都会让可读性变差。

这里,我要概括一下这一点,并谈论一下代码的不透明。显然,难以阅读的代码是不透明的。但也存在其他形式的不透明。比如代码的抽象很难理解,或者也许你有一个类,其方法之间调用复杂,从而使调用该类的逻辑不透明。

一般来说,人们希望能看到能够清晰地表达其意图和目的的代码。而不透明性掩盖了这一点,导致代码质量变差。

5. 缺少自动化测试

是的,自动化测试可以帮助你发现错误。但在我看来,它对代码质量的最大影响并不是这个。一个健壮、维护良好的自动化测试套件能让你对修改代码充满信心。

修改代码的信心并不会直接转化为代码质量。但是,它能让你更加方便更有信心地重构代码,以改善上面所提到的所有问题:远离全局状态、消除重复代码、最小化依赖关系、并使你的代码更清晰更干净。

如果没有自动化测试套件,人们就不会有这种信心。他们会将代码看成是一种古老的设备,“它目前工作的很正常,所以,无论如何都不要去碰它”,这实际上就是代码质量差的标志。

避免劣质代码是一场战争

在谈论代码质量的时候,似乎代码的质量很容易判断。我花了很多时间与团队一起降低代码的总体拥有成本,因此客户那里的开发人员通常认为我是根据他们所做的工作来判断代码质量的。

但真的,我做得越多,我判断的就越少。首先,我不知道他们遇到了什么样的困难,代码是如何成为现在这样子的。代码库会很自然地变得乱七八糟,除非你认真努力去防止这种情况的发生。劣质代码是默认状态。所以并不是说他们写了劣质代码,而是他们没有时间和资源去阻止代码变差。

认识到这种观点对团队和管理者都是有益的。除非你投资于你的团队、你的教育和你的发展过程,否则代码的维护成本会越来越高,而且质量也会越来越差。所以,请立即行动起来吧,防止出现我在这里讨论过的问题。

原创粉丝点击