
今天我在看到一個程序員發布了一個非常有趣的代碼片段(非常高效的代碼)。
這段代碼像病毒一樣傳播開來,你可能已經在不同的平臺上看到過它。

關于這個話題有許多爭論。一些人認為有更短(也許也更好)的版本來做同樣的工作。
例如,我請求ChatGPT重寫一個更短的版本,得到如下結果:

是不是越短越好?
說實話,我對原版的反應是,什么鬼?我暗自發笑,認為我可以在5分鐘內使用map或類似的巧妙技術對其進行重構。然而,喝了杯咖啡后,我又看了看代碼片段。我發現意圖非常明確,諷刺的是,map版本需要更多的時間閱讀。
對于經驗豐富的開發人員來說,較短的版本可能需要幾秒鐘才能弄清楚發生了什么。如果代碼是幾周前編寫的,嗯,可能需要多花幾分鐘時間才能理解。
原始代碼有什么問題?
盡管第一個版本的代碼看起來簡單明了,但它有一個缺點,就是不能將表示和業務邏輯結合起來。軟件被設計為具有靈活性和適應性,這個版本的代碼使得將來更難進行更改。
說它混合了表現和邏輯,我的意思是,如果明天我們想顯示一個紅點(而不是藍色的),我們必須修改相當多的地方。
除此之外,我想先解決一個與邏輯泄漏有關的小問題。你可能已經注意到,它多次重復precentage> x &&precentage<= y,我將提取一個函數,使其更具可讀性:
const isPercentageInRange = (number: number, low: number, high: number) =>
number > low && number <= high;
如果我將百分比檢查分成兩個函數,并將藍色和白色的點畫在兩個函數中,并將結果安排在新的getPercentageRounds中,代碼將如下所示:
const getBandByPercentage = (percentage: number) => {
if (percentage === 0) return 0;
if (isPercentageInRange(percentage, 0.0, 0.1)) return 1;
if (isPercentageInRange(percentage, 0.1, 0.2)) return 2;
if (isPercentageInRange(percentage, 0.2, 0.3)) return 3;
if (isPercentageInRange(percentage, 0.3, 0.4)) return 4;
if (isPercentageInRange(percentage, 0.4, 0.5)) return 5;
if (isPercentageInRange(percentage, 0.5, 0.6)) return 6;
if (isPercentageInRange(percentage, 0.6, 0.7)) return 7;
if (isPercentageInRange(percentage, 0.7, 0.8)) return 8;
if (isPercentageInRange(percentage, 0.8, 0.9)) return 9;
return 10;
};
const drawProgress = (percentage: number) => {
const band = getBandByPercentage(percentage);
return new Array(10).fill("", 0, band).fill("?", band, 10);
};
const getPercentageRounds = (percentage: number) => {
return drawProgress(percentage).join("")
}
函數getBandByPercentage將百分比映射到一個范圍(或級別),而drawProgress根據范圍繪制圓點。
讓演示更加靈活。
我們可以提取藍白色的點作為參數,讓進度條更加靈活。此外,為了保持當前行為,我們可以使用當前值作為默認值:
const drawProgress = (
percentage: number,
done: string = "",
doing: string = "?"
) => {
const band = getBandByPercentage(percentage);
return new Array(10).fill(done, 0, band).fill(doing, band, 10);
};
然后可以在命令行中創建一個進度條,如下所示:
const getPercentageRounds = (percentage: number) => {
return drawProgress(0.3, '#', '=').join("")
}
如果想讓進度條變寬,可以傳入一個較長的版本字符串,表示“完成”和“正在做”:
const getPercentageRounds = (percentage: number) => {
return drawProgress(0.3, '##', '==').join("")
}
所以我們可以有不同的進度條,短的和長的,藍色和紅色的。

代碼配置
重構之后,表示和邏輯被拆分。我不喜歡使用這么大的if-else語句塊:
const getBandByPercentage = (percentage: number) => {
if (percentage === 0) return 0;
if (isPercentageInRange(percentage, 0.0, 0.1)) return 1;
if (isPercentageInRange(percentage, 0.1, 0.2)) return 2;
if (isPercentageInRange(percentage, 0.2, 0.3)) return 3;
if (isPercentageInRange(percentage, 0.3, 0.4)) return 4;
if (isPercentageInRange(percentage, 0.4, 0.5)) return 5;
if (isPercentageInRange(percentage, 0.5, 0.6)) return 6;
if (isPercentageInRange(percentage, 0.6, 0.7)) return 7;
if (isPercentageInRange(percentage, 0.7, 0.8)) return 8;
if (isPercentageInRange(percentage, 0.8, 0.9)) return 9;
return 10;
};
正如Martin Fowler的文章所討論的,在某些情況下,將“代碼”拆分到配置文件中是有益的。
我們可以將這個百分比移動到band mapping中,比如(甚至可以將bandConfig移動到JSON文件中):
const bandConfig: BandConfig[] = [
{
range: [-Infinity, 0.0],
band: 0,
},
{
range: [0.0, 0.1],
band: 1,
},
//...
];
然后getBandByPercentage可以簡化為
const getBandByPercentage = (percentage: number) => {
const config = bandConfig.find((c) => {
const [low, high] = c.range;
return isPercentageInRange(percentage, low, high)
});
return config?.band;
};
隨著復雜性轉移到配置文件,getBandByPercentage函數只剩下幾行了。
重用邏輯?
讓我再演示一個用例來展示拆分可以帶來什么。現在假設我們想在Web UI中使用進度條——例如ProgressBar組件。
導入drawProgress函數非常容易:
const ProgressBar = ({
percentage,
}: {
percentage: number;
done?: string;
doing?: string;
}) => {
return (
<>
{drawProgress(percentage).map((character) => (
<span>{character}</span>
))}
</>
);
};
在頁面上,我們可以看到這樣的內容:

我們可以輕松地更改組件中的句點字符,并使進度條更容易適應新的UI需求。
總結
最終的結果可能沒有原始結果那么“高效”。盡管如此,通過明確的關注點分離(表示和業務邏輯,以及邏輯和配置),它可以對新需求做出更積極的響應。