為什麼我們學習編程的第一條指令應該是最後使用的。
沒有人再使用 GOTO 指令,而且很少有編程語言仍然支持它。
我們已經成熟并确認意大利面條代碼是不可維護且容易出錯的。 結構化編程幾年前就解決了這個問題。
多虧了 Edsger Dijkstra 令人難以置信的論文:Go To Statement Considered Harmful,我們擺脫了這句話。
下一個演變步驟将是删除大多數 If 語句
ifs/Cases 和 Switches 是僞裝成結構化流程的 GOTO。
我們的工具将是面向對象的編程原則。
問題
大多數 IF 句都與偶然的決定有關。 這種耦合會産生連鎖反應,使代碼更難維護。
If 語句違反了開放/封閉原則。 我們的設計将不那麼可擴展并且無法擴展。
更重要的是,Ifs 為更嚴重的問題敞開了大門,例如開關、案例、默認值、返回、繼續和中斷。
它們使我們的算法更暗,并迫使我們構建意外複雜的解決方案。
軟件開發人員無法解釋為什麼我們使用這個分支語句。 這是一種代碼氣味。
解決方案
在我們繼續删除 IF 語句之前,我們應該确定它是必要的還是偶然的 If。
基本 IF
讓我們看一個基本的 IF 語句
class Moviegoer {
constructor(age) {
this.age = age;
}
watchXRatedMovie() {
if (this.age < 18)
throw new Error("You are not allowed to watch this movie");
else
this.watchMovie();
}
watchMovie() {
// ..
}
}
let jane = new Moviegoer(12);
jane.watchXRatedMovie();
// Throws exception since Jane is too young to watch the movie
我們應該決定是否删除這個 if 句子。
我們必須了解它是代表業務規則(必要)還是實現工件(意外)。
在上述情況下,我們将尊重我們的雙射。 所以我們不會替換 if。
現實世界中的人們使用 IF 用自然語言描述年齡限制
意外 IF
現在讓我們深入研究糟糕的 IF。
class Movie {
constructor(rate) {
this.rate = rate;
}
}
class Moviegoer {
constructor(age) {
this.age = age;
}
watchMovie(movie) {
if ((this.age < 18) && (movie.rate == 'Adults Only'))
throw new Error("You are not allowed to watch this movie");
// watch movie
}
}
let jane = new Moviegoer(12);
let theExorcist = new Movie('Adults Only');
jane.watchMovie(theExorcist);
// Jane cannot watch the exorcist since she is 12
電影分級 IF 與真實世界 If 無關,而是與意外(和耦合)實現有關。
我們的設計決定是用字符串對評級進行建模。
這是一個既不開放擴展也不封閉修改的經典解決方案。
讓我們看看新要求會發生什麼。
class Movie {
constructor(rate) {
this.rate = rate;
}
}
class Moviegoer {
constructor(age) {
this.age = age;
}
watchMovie(movie) {
//!!!!!!!!!!!!!!!!! IFS ARE POLLUTING HERE !!!!!!!!!!!!!!!!!!!!!!!!!!
if ((this.age < 18) && (movie.rate == 'Adults Only'))
throw new Error("You are not allowed to watch this movie");
else if ((this.age < 13) && (movie.rate == 'PG 13'))
throw new Error("You are not allowed to watch this movie");
// !!!!!!!!!!!!!!!! IFS ARE POLLUTING HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!
// watch movie
}
}
let theExorcist = new Movie('Adults Only');
let gremlins = new Movie('PG 13');
let jane = new Moviegoer(12);
jane.watchMovie(theExorcist);
// Jane cannot watch the exorcist since she is 12
jane.watchMovie(gremlins);
// Jane cannot watch gremlins since she is 12
let joe = new Moviegoer(16);
joe.watchMovie(theExorcist);
// Joe cannot watch the exorcist since he is 16
joe.watchMovie(gremlins);
// Joe CAN watch gremlins since he is 16
我們可以檢測到一些代碼氣味:
食譜
讓我們通過以下步驟解決這個問題:
1 - 為每個 IF 條件創建一個多态層次結構(如果它不存在)。
2 - 将每個 IF 主體移動到前一個抽象。
3 - 用多态方法調用替換 IF 調用。
在我們的示例中:
// 1. Create a Polymorphic Hierarchy for every IF condition
// (if it doesn't already exist)
class MovieRate {
// If language permits this should be declared abstract
}
class PG13MovieRate extends MovieRate {
//2. Move every *IF Body* to the former abstraction
warnIfNotAllowed(age) {
if (age < 13)
throw new Error("You are not allowed to watch this movie");
}
}
class AdultsOnlyMovieRate extends MovieRate {
//2. Move every *IF Body* to the former abstraction
warnIfNotAllowed(age) {
if (age < 18)
throw new Error("You are not allowed to watch this movie");
}
}
class Movie {
constructor(rate) {
this.rate = rate;
}
}
class Moviegoer {
constructor(age) {
this.age = age;
}
watchMovie(movie) {
// 3. Replace IF Call by polymorphic method call
movie.rate.warnIfNotAllowed(this.age);
// watch movie
}
}
let theExorcist = new Movie(new AdultsOnlyMovieRate());
let gremlins = new Movie(new PG13MovieRate());
let jane = new Moviegoer(12);
// jane.watchMovie(theExorcist);
// Jane cannot watch the exorcist since she is 12
// jane.watchMovie(gremlins);
// Jane cannot watch gremlins since she is 12
let joe = new Moviegoer(16);
// joe.watchMovie(theExorcist);
// Joe cannot watch the exorcist since he is 16
joe.watchMovie(gremlins);
// Joe CAN watch gremlins since he is 16
有了這個結果:
1- 代碼被 IF 污染
我們不應該再添加 IFS。 擴展模型就足夠了。
2-缺少默認語句
在這種情況下,不需要默認行為,因為異常會中斷流程。 在很多時候,一個 Null 對象就足夠了。
3-新的評級将帶來新的IF
我們将通過多态新實例來解決它。
4- 代表評級的字符串不是一流的對象。 錯字将引入難以發現的錯誤。
這隐藏在 Ratings 實現中。
5- 我們被迫在電影上添加吸氣劑來做出決定。
打破這種協作鍊
movie.rate.warnIfNotAllowed(this.age);
class Movie {
constructor(rate) {
this._rate = rate; // Rate is now private
}
warnIfNotAllowed(age) {
this._rate.warnIfNotAllowed(age);
}
}
class Moviegoer {
constructor(age) {
this.age = age;
}
watchMovie(movie) {
movie.warnIfNotAllowed(this.age);
// watch movie
}
}
評級是私有的,所以我們不會破壞封裝。
因此,我們可以安全地避免 getter。
将配方應用于所有 IF 條件
現在我們有了秘密公式,我們可以更進一步,嘗試删除與年齡相關的基本 IF 條件。
class Age {
}
class AgeLessThan13 extends Age {
assertCanWatchPG13Movie() {
throw new Error("You are not allowed to watch this movie");
}
assertCanWatchAdultMovie() {
throw new Error("You are not allowed to watch this movie");
}
}
class AgeBetween13And18 extends Age {
assertCanWatchPG13Movie() {
// No Problem
}
assertCanWatchAdultMovie() {
throw new Error("You are not allowed to watch this movie");
}
}
class MovieRate {
// If language permits this should be declared abstract
// abstract assertCanWatch();
}
class PG13MovieRate extends MovieRate {
//2. Move every *IF Body* to the former abstraction
assertCanWatch(age) {
age.assertCanWatchPG13Movie()
}
}
class AdultsOnlyMovieRate extends MovieRate {
//2. Move every *IF Body* to the former abstraction
assertCanWatch(age) {
age.assertCanWatchAdultMovie()
}
}
class Movie {
constructor(rate) {
this._rate = rate; // Rate is now private
}
watchByMe(moviegoer) {
this._rate.assertCanWatch(moviegoer.age);
}
}
class Moviegoer {
constructor(age) {
this.age = age;
}
watchMovie(movie) {
movie.watchByMe(this);
}
}
let theExorcist = new Movie(new AdultsOnlyMovieRate());
let gremlins = new Movie(new PG13MovieRate());
let jane = new Moviegoer(new AgeLessThan13());
// jane.watchMovie(theExorcist);
// Jane cannot watch the exorcist since she is 12
// jane.watchMovie(gremlins);
// Jane cannot watch gremlins since she is 12
let joe = new Moviegoer(new AgeBetween13And18());
// joe.watchMovie(theExorcist);
// Joe cannot watch the exorcist since he is 16
joe.watchMovie(gremlins);
// Joe CAN watch gremlins since he is 16
我們更換了所有的 IF。 在後一種情況下使用雙重調度技術
我們使用了我們的公式,它奏效了。 但有一種過度設計的味道。
我們應該避免最後的設計,并在必要條件和偶然條件之間設置明确的界限。
一個好的設計規則是,如果它們屬于同一領域(電影和收視率),則創建抽象,如果它們跨領域(電影和年齡),則不要這樣做。
Ifs很臭嗎?
根據上面顯示的證據。 我們應該将許多 IF 視為代碼異味,并用我們的方法解決它們。
為什麼會這樣?
本文(和許多其他文章)建議避免使用大多數 IF 句。 對于所有對其使用非常滿意的開發人員來說,這将是非常困難的。
請記住,懶惰和隐藏的假設非常植根于我們的職業。 幾十年來,我們一直(ab)使用 IF,我們的軟件并不是它的最佳版本。
結論
使用這種簡單的技術,我們将能夠以程序的方式删除所有意外的 if。
這将使我們的模型更少耦合,更廣泛。
關注七爪網,獲取更多APP/小程序/網站源碼資源!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!