讓硬編碼成為你的默認選擇

jopen 9年前發布 | 13K 次閱讀 硬編碼

硬編碼【注1】經常被認為反面模式【注2】。把隨著時間而變化的這些值,硬編碼到源代碼里,每當這些值真正變化時,都需要重新編譯。

然而這個陳述也是正確的,我覺得,當開發一個應用程序時,硬編碼應該成為默認選擇。

讓硬編碼成為你的默認選擇

硬編碼 VS 配置文件

當你忙于一個項目或功能時,總是有一些魔法數字或字符串,它們潛在地會在將來變化。第一個沖動就是讓這些變化可配置,今后你就能輕松修改它們。

對于大多數情況,這種決定使得接下來的維護復雜化。我們這里所面對的,是一個經典的困境「簡單 VS 敏捷」。隨著應用程序的增大,修改它的某些參數變得更加容易,因為它們被提取到配置文件里,但同時總體維護負擔增加了。

在極端情況下,你或許最終得到了數十個、甚至數百個可配置的參數,這使得維護應用程序變得極度痛苦。這種情形被叫做配置地獄(configuration hell)。

就和其它很多軟件設計決定一樣,我們需要求助于 YAGNI【注3】原則。所有這些參數真的需要馬上可配置嗎?或者,我們只是為了提前安排好?如果屬于后者的情況,當這個需要變得明顯之前,我們最好砍掉配置文件,以保持其小巧。

如果該必要性沒有被證明,那么硬編碼就應該是默認選擇。對于硬編碼,我的意思不是說,你應該把魔法數字和字符串擴散到你的項目源代碼里。它們仍然需要收集并被放在一個地方做為常量。然而,這意味著你應該將它們從配置文件移除。

日志的例子

讓我們舉些例子,看看我們在實踐中該怎樣應用上面提到的原則。

我喜愛的日志資源庫是 NLog。它有著相當豐富的功能,每一項都可輕松配置。

下面是一個典型的 NLog 安裝的配置文件:

<nlog>
  <variable name=“logFile“ value=“C:\logs\log-${shortdate}.txt“/>

<targets> <target name=“trace“ xsi:type=“AsyncWrapper“ queueLimit=“5000“ overflowAction=“Block“> <target name=“file“ xsi:type=“File“ encoding=“utf-8“ layout=“Date: ${longdate}\r\n Level: ${level}\r\n Message: ${message}\r\n“ fileName=“${logFile}.txt“ archiveFileName=“${logFile}.{#####}.txt“ archiveAboveSize=“2097152“ maxArchiveFiles=“200“ archiveNumbering=“Sequence“/> </target> </targets>

<rules> <logger name=“*“ minlevel=“Warn“ writeTo=“trace“/> </rules> </nlog></code></pre>

盡管設置本身相當合理,我還是想提出一個疑問:把所有這些設置放在配置文件里,真的有必要嗎?我們將要修改它們嗎?在大多數情況下,答案是不。即使你對此感到懷疑,根據 YAGNI 原則,那也意味著「不」。

幸運的是,NLog 允許我們使用其配置的 API,以在代碼里做配置。因此,除了依賴于配置文件,我們能夠輕松地將這些設置挪到源代碼里。我們仔細看下這個例子,看看我們可以除掉哪些設置。盡管設置本身相當合理,我還是想提出一個疑問:把所有這些設置放在配置文件里,真的有必要嗎?我們將要修改它們嗎?在大多數情況下,答案是不。即使你對此感到懷疑,根據 YAGNI 原則,那也意味著「不」。

首先,在 targets 部分,你可以看到我們為真正的 target 使用了 async wrapper。我們真的想讓它可配置嗎?不,這種設置很少需要修改。好的,其它的 target 呢?它設置了很多有用的屬性,比如日志的 layoutfile namemaximum log file size 等等。我們真的需要這種「不用重新配置就可修改」的機會嗎?很可能,不需要。

rules 部分呢?這部分并不像 targets 部分那樣明顯。為了觸發規則而修改 最小日志等級(minlevel)的可能性貌似可以理解,因為我們或許因為調試的原因想在運行中修改它。然而,問題在于實踐中我們從來不會這樣做。因此,我們最好也移除這個設置。

好了,我們最終還剩下什么?僅有一個設置留下了,它就是日志文件本身的路徑:

<appSettings>
  <add key=“LogFilePath“ value=“C:\logs\log-${shortdate}.txt“ />
</appSettings>

現在,所有其它設置被放在了源代碼里:

string layout = “Date: ${longdate}\r\n“ +
    “Level: ${level}\r\n“ +
    “Message: ${message}\r\n“;

var config = new LoggingConfiguration();

var target = new FileTarget { FileName = fileName, Layout = layout / Other params / }; var asyncWrapper = new AsyncTargetWrapper(target) { QueueLimit = 5000, OverflowAction = AsyncTargetWrapperOverflowAction.Block }; var rule = new LoggingRule(“*”, LogLevel.Error, asyncWrapper); config.AddTarget(“asyncWrapper”, asyncWrapper); config.LoggingRules.Add(rule);

LogManager.Configuration = config;</code></pre>

你可以看到,我們移除了整個 nlog 部分,并把保留的設置挪入了 appSettings 部分。現在它變成了我們配置文件的普通一員。

唯一的設置就是,根據被應用的環境,真的需要具有不同的值。我們這里做的工作,就是減少表面配置,從而使得解決方案更加可維護,代價是少了一些靈活。我堅信這是不錯的折衷方案。

隨后,我們或許發現我們自己經常修改某項硬編碼設置。這就發出了信號,我們找到了將其挪入配置文件的正當理由。但截至目前,就讓硬編碼成為你的默認選擇吧。

總結

我經常把這個規則應用到潛在地可被挪入配置文件的所有設置上,這有助于使得它們小而可維護。還有,我注意到,即使我偶爾需要修改某些配置,直接在源代碼里修改,已經足夠應付大部分情況了。

[更新]

我需要指出,本文的內容僅適用于內部用的軟件(in-house software)【注4】。第三方資源庫開發是不同的故事

還有,我真的感激我在本文得到的所有反饋,想不到會引發如此多的討論。但是,不要把本文的主旨——「讓硬編碼成為你的默認選擇」——同「讓硬編碼成為你的唯一選擇」混為一談。如果你真的需要從代碼里提取某些值、使其可配置,你最好就這樣做。我向你提倡的唯一一點就是自問一下,這種提取是否真的有必要。


  • 注1:硬編碼(英語:Hard Code或Hard Coding)指的是在軟件實現上,把輸出或輸入的相關參數(例如:路徑、輸出的形式或格式)直接以常量的方式書寫在源代碼中,而非在運行時期由外界指定的設置、資源、數據或格式做出適當回應。一般被認定是種反模式或不完美的實現,因為軟件受到輸入數據或輸出的格式改變就必需修改源代碼,對客戶而言,改變源代碼之外的小設置也許還比較容易。
  • 注2:在軟件工程中,一個反面模式(anti-pattern或antipattern)指的是在實踐中明顯出現但又低效或是有待優化的設計模式,是用來解決問題的帶有共同性的不良方法。它們已經經過研究并分類,以防止日后重蹈覆轍,并能在研發尚未投產的系統時辨認出來。
  • 注3:”You aren’t gonna need it” (acronym: YAGNI) is a principle of extreme programming (XP) that states a programmer should not add functionality until deemed necessary。
  • 注4:In-house software is a software that is produced by a corporate entity for purpose of using it within the organization。另外,在阮一峰的《Joel Spolsky在耶魯大學的演講》一文里,有更為風趣的介紹。
  • </ul> 來源: http://www.labazhou.net/2015/06/make-hard-coding-your-default-choice/

     本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!