为什么我们学习编程的第一条指令应该是最后使用的。

if编程实例(如何永远摆脱烦人的)(1)

没有人再使用 GOTO 指令,而且很少有编程语言仍然支持它。

我们已经成熟并确认意大利面条代码是不可维护且容易出错的。 结构化编程几年前就解决了这个问题。

多亏了 Edsger Dijkstra 令人难以置信的论文:Go To Statement Considered Harmful,我们摆脱了这句话。

下一个演变步骤将是删除大多数 If 语句

ifs/Cases 和 Switches 是伪装成结构化流程的 GOTO。

我们的工具将是面向对象的编程原则。

if编程实例(如何永远摆脱烦人的)(2)

问题

大多数 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. 缺少默认语句。
  3. 新的评级将带来新的IF。
  4. 代表评级的字符串不是一流的对象。 错字将引入难以发现的错误。
  5. 我们被迫在 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。 在后一种情况下使用双重调度技术

我们使用了我们的公式,它奏效了。 但有一种过度设计的味道。

  1. 代表年龄的类与我们模型上的真实概念无关。
  2. 模型太复杂。
  3. 我们将需要与新年龄段相关的新课程。
  4. 年龄组可能不相交。

我们应该避免最后的设计,并在必要条件和偶然条件之间设置明确的界限。

一个好的设计规则是,如果它们属于同一领域(电影和收视率),则创建抽象,如果它们跨领域(电影和年龄),则不要这样做。

Ifs很臭吗?

根据上面显示的证据。 我们应该将许多 IF 视为代码异味,并用我们的方法解决它们。

为什么会这样?

本文(和许多其他文章)建议避免使用大多数 IF 句。 对于所有对其使用非常满意的开发人员来说,这将是非常困难的。

请记住,懒惰和隐藏的假设非常植根于我们的职业。 几十年来,我们一直(ab)使用 IF,我们的软件并不是它的最佳版本。

结论

使用这种简单的技术,我们将能够以程序的方式删除所有意外的 if。

这将使我们的模型更少耦合,更广泛。

关注七爪网,获取更多APP/小程序/网站源码资源!

,