为什么我们学习编程的第一条指令应该是最后使用的。
没有人再使用 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
我们可以检测到一些代码气味:
- 代码被 IF.
- 缺少默认语句。
- 新的评级将带来新的IF。
- 代表评级的字符串不是一流的对象。 错字将引入难以发现的错误。
- 我们被迫在 Movies 上添加 getter 来做出决定。
食谱
让我们通过以下步骤解决这个问题:
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/小程序/网站源码资源!
,