劣質代碼產生的五個原因

此文來自: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. 缺少自動化測試

是的,自動化測試可以幫助你發現錯誤。但在我看來,它對代碼質量的最大影響並不是這個。一個健壯、維護良好的自動化測試套件能讓你對修改代碼充滿信心。

修改代碼的信心並不會直接轉化爲代碼質量。但是,它能讓你更加方便更有信心地重構代碼,以改善上面所提到的所有問題:遠離全局狀態、消除重複代碼、最小化依賴關係、並使你的代碼更清晰更乾淨。

如果沒有自動化測試套件,人們就不會有這種信心。他們會將代碼看成是一種古老的設備,“它目前工作的很正常,所以,無論如何都不要去碰它”,這實際上就是代碼質量差的標誌。

避免劣質代碼是一場戰爭

在談論代碼質量的時候,似乎代碼的質量很容易判斷。我花了很多時間與團隊一起降低代碼的總體擁有成本,因此客戶那裏的開發人員通常認爲我是根據他們所做的工作來判斷代碼質量的。

但真的,我做得越多,我判斷的就越少。首先,我不知道他們遇到了什麼樣的困難,代碼是如何成爲現在這樣子的。代碼庫會很自然地變得亂七八糟,除非你認真努力去防止這種情況的發生。劣質代碼是默認狀態。所以並不是說他們寫了劣質代碼,而是他們沒有時間和資源去阻止代碼變差。

認識到這種觀點對團隊和管理者都是有益的。除非你投資於你的團隊、你的教育和你的發展過程,否則代碼的維護成本會越來越高,而且質量也會越來越差。所以,請立即行動起來吧,防止出現我在這裏討論過的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章