有没有更好的方法来编写这样的嵌套 if-else?
Is there a better way to write a nested if-else such as this?
以下是返回小部件的 flutter 函数的代码片段。它可以是 returns 按钮或文本小部件,具体取决于四个不同的变量。
Widget _renderReservationState() {
if (reservationsDetailState.isFinished == false) {
if (reservationsDetailState.rsvpStatus == "Attending") {
if (reservationsDetailState.attendanceStatus == "Attended") {
return Center(
child: Text("Registration completed");)
} else {
if (reservationsDetailState.isEditing == false) {
return SizedBox(
child: Button(
onPressed: () {
if (myReservationsDetailState.isEditing) {
_setEditMode(false);
} else {
_setEditMode(true);
}
},
title: "Edit reservation"),
width: double.infinity);
} else {
return SizedBox(
child: Button(
onPressed: () {
if (reservationsDetailState.isEditing) {
_confirmReservation(dateController.text,
timeController.text, attendeeController.text);
} else {
_setEditMode(true);
}
},
title: "Confirm reservation"),
width: double.infinity);
}
}
} else {
return SizedBox(
child: Button(
onPressed: () => {
_confirmReservation(dateController.text,
timeController.text, attendeeController.text),
},
title: "Make a reservation"),
width: double.infinity);
}
} else {
if (myReservationsDetailState.attendanceStatus == "Attended") {
return Center(
child: Text("Thank you for your visit")
);
} else {
return Center(
child: Text("Please join next time");)
}
}
}
现有代码是几百行的嵌套 if-else。我已经将它重构为更短,但我对这个实现也不满意。如有任何建议或建议,我们将不胜感激。
如果您将代码拆分为许多较小的函数或 CustomWidget,您将帮自己一个忙并显着提高代码的可读性。这样你就可以摆脱极端的、不可读的嵌套。
这看起来像是对联合类型的很好使用。让我尝试将它与 if
s 和 switch
es 进行比较。
当您必须以各种不同的方式对值进行组合时,有时不可避免地会出现这样的代码。您的选择是:
- 嵌套
if
语句,就像您所做的那样
- 嵌套
switch
语句
- 将联合类型与包一起使用,例如 freezed。
让我们考虑一下各自的优缺点:
- 嵌套
if
语句:
- 易于上手
- 最少的缩进量
- (!) 用
||
分组案例有点尴尬
- (!)有些情况容易忘记
- (!) 出现新情况时要记得编辑代码
- 嵌套
switch
语句:
- 相对容易打字
- 可以分组案例
- (!) 额外的缩进级别
- (!) 个案例共享局部变量
- 使用枚举,IDE 可以为缺失的案例提供警告。所以下面两个可以缓解
- (!)有些情况容易忘记
- (!) 出现新情况时要记得编辑代码
- 联合类型
- 强制你涵盖所有情况。使忘记案例成为 compile-time 错误。
- 添加新案例时,现有代码无法编译,除非您覆盖所有新案例。
- 案例成为对象而不是简单的值,您可以为它们添加额外的职责。
- (!) 冻结不可能对案例进行分组,只有一个“其他”案例
orElse
。
- (!) 额外级别的缩进和额外的闭包语法
- (!) 需要一个额外的步骤。使用 freezed 你必须创建那些 classess 和 运行 build_runner 来生成代码。
我相信这些是主要的选择。当您处于这种情况并且担心自己或队友有一天会忘记某个案例时,尝试 union classes with freezed 可能是个好主意。尽管带来不便,但它提供的覆盖所有案例的思想是无价的。
从包中借用例子,当你有一个 union class 不同的情况如下:
@freezed
class Union with _$Union {
const factory Union(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String? message]) = ErrorDetails;
}
你这样用。不提供 when
中的任何参数是 compile-time 错误。所以你可以确定你不会忘记任何情况:
var union = Union(42);
print(
union.when(
(int value) => 'Data $data',
loading: () => 'loading',
error: (String? message) => 'Error: $message',
),
); // Data 42
在您的情况下,您可以为每个变量创建单独的联合类型,也可以创建表示这些变量组合的单个联合类型。所以你可以有
@freezed
class IsFinished with _$IsFinished {
const factory IsFinished.finished() = Finished;
const factory IsFinished.notFinished() = NotFinished;
}
@freezed
class RsvpStatus with _$RsvpStatus {
const factory RsvpStatus.none() = None;
const factory RsvpStatus.rsvpd() = Rsvpd;
const factory RsvpStatus.canceled() = Canceled;
}
// usage
Widget w = isFinished.when(
finished: () => isRsvpd.when(
none: () => Text('finished and no rsvp'),
rsvpd: () => Text('finished and rsvpd'),
cancelled: () => Text('finished and canceled'),
),
notFinished: () => isRsvpd.when(
none: () => Text('not finished and no rsvp'),
rsvpd: () => Text('not finished and rsvpd'),
cancelled: () => Text('not finished and canceled'),
),
);
或一张冻结的 class,其中包含 finishedRsvpd
等条目。在任何一种情况下,忘记大小写都将是一个 compile-time 错误,您会更放心地编写代码。
关于提高可读性的一些一般性建议:
去掉不必要的 if
检查:
if (someCondition) {
_setEditMode(false);
} else {
_setEditMode(true);
}
等同于:
_setEditMode(!someCondition);
避免if (condition == false)
。如果 condition
是 non-nullable,请使用 if (!condition)
。
尽可能将嵌套的 if
块折叠成 if
-else if
链:
if (condition1) {
...
} else {
if (condition2) {
...
} else {
...
}
}
可以取消缩进一个级别为:
if (condition1) {
...
} else if (condition2) {
...
} else {
...
}
不要写top-heavyif
-else
块。代码如下:
if (condition1) {
imagine();
that();
there();
are();
many();
many();
many();
lines();
ofCode();
if (condition2) {
withNested();
blocks();
} else {
that();
require();
scrolling();
}
} else {
someSingleLineOfCode();
}
通常比反转条件并将更短的块放在顶部更难遵循:
if (!condition1) {
someSingleLineOfCode();
} else {
imagine();
that();
there();
are();
many();
many();
many();
lines();
ofCode();
if (condition2) {
withNested();
blocks();
} else {
that();
require();
scrolling();
}
}
Unindent else
块利用提前退出:
if (condition1) {
return someWidget;
} else {
lots();
of();
other();
code();
return someOtherWidget;
}
可以是:
if (condition1) {
return someWidget;
}
lots();
of();
other();
code();
return someOtherWidget;
尽量强调不同案例中哪些部分相同,哪些部分不同。示例:
if (myReservationsDetailState.attendanceStatus == "Attended") {
return Center(child: Text("Thank you for your visit"));
} else {
return Center(child: Text("Please join next time"));
}
可以变成:
return Center(
child: Text(myReservationsDetailState.attendanceStatus == "Attended"
? "Thank you for your visit"
: "Please join next time"));
对于具有多个相似 SizedBox(child: Button(...))
案例的部分,我会创建局部变量来捕获差异并重新排列代码以便共享公共结构。
综合起来:
Widget _renderReservationState() {
if (reservationsDetailState.isFinished) {
return Center(
child: Text(myReservationsDetailState.attendanceStatus == "Attended"
? "Thank you for your visit"
: "Please join next time"));
}
if (reservationsDetailState.rsvpStatus == "Attending" &&
reservationsDetailState.attendanceStatus == "Attended") {
return Center(child: Text("Registration completed"));
}
VoidCallback onButtonPressed;
String buttonTitle;
if (reservationsDetailState.rsvpStatus != "Attending") {
onButtonPressed = () => _confirmReservation(
dateController.text, timeController.text, attendeeController.text);
buttonTitle = "Make a reservation";
} else if (!reservationsDetailState.isEditing) {
onButtonPressed = () => _setEditMode(!myReservationsDetailState.isEditing);
buttonTitle = "Edit reservation";
} else {
onButtonPressed = () {
if (reservationsDetailState.isEditing) {
_confirmReservation(
dateController.text, timeController.text, attendeeController.text);
} else {
_setEditMode(true);
}
};
buttonTitle = "Confirm reservation";
}
return SizedBox(
child: Button(onPressed: onButtonPressed, title: buttonTitle),
width: double.infinity);
}
它仍然很丑,但缩进明显减少了,在我看来比原来更好。
以下是返回小部件的 flutter 函数的代码片段。它可以是 returns 按钮或文本小部件,具体取决于四个不同的变量。
Widget _renderReservationState() {
if (reservationsDetailState.isFinished == false) {
if (reservationsDetailState.rsvpStatus == "Attending") {
if (reservationsDetailState.attendanceStatus == "Attended") {
return Center(
child: Text("Registration completed");)
} else {
if (reservationsDetailState.isEditing == false) {
return SizedBox(
child: Button(
onPressed: () {
if (myReservationsDetailState.isEditing) {
_setEditMode(false);
} else {
_setEditMode(true);
}
},
title: "Edit reservation"),
width: double.infinity);
} else {
return SizedBox(
child: Button(
onPressed: () {
if (reservationsDetailState.isEditing) {
_confirmReservation(dateController.text,
timeController.text, attendeeController.text);
} else {
_setEditMode(true);
}
},
title: "Confirm reservation"),
width: double.infinity);
}
}
} else {
return SizedBox(
child: Button(
onPressed: () => {
_confirmReservation(dateController.text,
timeController.text, attendeeController.text),
},
title: "Make a reservation"),
width: double.infinity);
}
} else {
if (myReservationsDetailState.attendanceStatus == "Attended") {
return Center(
child: Text("Thank you for your visit")
);
} else {
return Center(
child: Text("Please join next time");)
}
}
}
现有代码是几百行的嵌套 if-else。我已经将它重构为更短,但我对这个实现也不满意。如有任何建议或建议,我们将不胜感激。
如果您将代码拆分为许多较小的函数或 CustomWidget,您将帮自己一个忙并显着提高代码的可读性。这样你就可以摆脱极端的、不可读的嵌套。
这看起来像是对联合类型的很好使用。让我尝试将它与 if
s 和 switch
es 进行比较。
当您必须以各种不同的方式对值进行组合时,有时不可避免地会出现这样的代码。您的选择是:
- 嵌套
if
语句,就像您所做的那样 - 嵌套
switch
语句 - 将联合类型与包一起使用,例如 freezed。
让我们考虑一下各自的优缺点:
- 嵌套
if
语句:
- 易于上手
- 最少的缩进量
- (!) 用
||
分组案例有点尴尬 - (!)有些情况容易忘记
- (!) 出现新情况时要记得编辑代码
- 嵌套
switch
语句:
- 相对容易打字
- 可以分组案例
- (!) 额外的缩进级别
- (!) 个案例共享局部变量
- 使用枚举,IDE 可以为缺失的案例提供警告。所以下面两个可以缓解
- (!)有些情况容易忘记
- (!) 出现新情况时要记得编辑代码
- 联合类型
- 强制你涵盖所有情况。使忘记案例成为 compile-time 错误。
- 添加新案例时,现有代码无法编译,除非您覆盖所有新案例。
- 案例成为对象而不是简单的值,您可以为它们添加额外的职责。
- (!) 冻结不可能对案例进行分组,只有一个“其他”案例
orElse
。 - (!) 额外级别的缩进和额外的闭包语法
- (!) 需要一个额外的步骤。使用 freezed 你必须创建那些 classess 和 运行 build_runner 来生成代码。
我相信这些是主要的选择。当您处于这种情况并且担心自己或队友有一天会忘记某个案例时,尝试 union classes with freezed 可能是个好主意。尽管带来不便,但它提供的覆盖所有案例的思想是无价的。
从包中借用例子,当你有一个 union class 不同的情况如下:
@freezed
class Union with _$Union {
const factory Union(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String? message]) = ErrorDetails;
}
你这样用。不提供 when
中的任何参数是 compile-time 错误。所以你可以确定你不会忘记任何情况:
var union = Union(42);
print(
union.when(
(int value) => 'Data $data',
loading: () => 'loading',
error: (String? message) => 'Error: $message',
),
); // Data 42
在您的情况下,您可以为每个变量创建单独的联合类型,也可以创建表示这些变量组合的单个联合类型。所以你可以有
@freezed
class IsFinished with _$IsFinished {
const factory IsFinished.finished() = Finished;
const factory IsFinished.notFinished() = NotFinished;
}
@freezed
class RsvpStatus with _$RsvpStatus {
const factory RsvpStatus.none() = None;
const factory RsvpStatus.rsvpd() = Rsvpd;
const factory RsvpStatus.canceled() = Canceled;
}
// usage
Widget w = isFinished.when(
finished: () => isRsvpd.when(
none: () => Text('finished and no rsvp'),
rsvpd: () => Text('finished and rsvpd'),
cancelled: () => Text('finished and canceled'),
),
notFinished: () => isRsvpd.when(
none: () => Text('not finished and no rsvp'),
rsvpd: () => Text('not finished and rsvpd'),
cancelled: () => Text('not finished and canceled'),
),
);
或一张冻结的 class,其中包含 finishedRsvpd
等条目。在任何一种情况下,忘记大小写都将是一个 compile-time 错误,您会更放心地编写代码。
关于提高可读性的一些一般性建议:
去掉不必要的
if
检查:if (someCondition) { _setEditMode(false); } else { _setEditMode(true); }
等同于:
_setEditMode(!someCondition);
避免
if (condition == false)
。如果condition
是 non-nullable,请使用if (!condition)
。尽可能将嵌套的
if
块折叠成if
-else if
链:if (condition1) { ... } else { if (condition2) { ... } else { ... } }
可以取消缩进一个级别为:
if (condition1) { ... } else if (condition2) { ... } else { ... }
不要写top-heavy
if
-else
块。代码如下:if (condition1) { imagine(); that(); there(); are(); many(); many(); many(); lines(); ofCode(); if (condition2) { withNested(); blocks(); } else { that(); require(); scrolling(); } } else { someSingleLineOfCode(); }
通常比反转条件并将更短的块放在顶部更难遵循:
if (!condition1) { someSingleLineOfCode(); } else { imagine(); that(); there(); are(); many(); many(); many(); lines(); ofCode(); if (condition2) { withNested(); blocks(); } else { that(); require(); scrolling(); } }
Unindent
else
块利用提前退出:if (condition1) { return someWidget; } else { lots(); of(); other(); code(); return someOtherWidget; }
可以是:
if (condition1) { return someWidget; } lots(); of(); other(); code(); return someOtherWidget;
尽量强调不同案例中哪些部分相同,哪些部分不同。示例:
if (myReservationsDetailState.attendanceStatus == "Attended") { return Center(child: Text("Thank you for your visit")); } else { return Center(child: Text("Please join next time")); }
可以变成:
return Center( child: Text(myReservationsDetailState.attendanceStatus == "Attended" ? "Thank you for your visit" : "Please join next time"));
对于具有多个相似
SizedBox(child: Button(...))
案例的部分,我会创建局部变量来捕获差异并重新排列代码以便共享公共结构。
综合起来:
Widget _renderReservationState() {
if (reservationsDetailState.isFinished) {
return Center(
child: Text(myReservationsDetailState.attendanceStatus == "Attended"
? "Thank you for your visit"
: "Please join next time"));
}
if (reservationsDetailState.rsvpStatus == "Attending" &&
reservationsDetailState.attendanceStatus == "Attended") {
return Center(child: Text("Registration completed"));
}
VoidCallback onButtonPressed;
String buttonTitle;
if (reservationsDetailState.rsvpStatus != "Attending") {
onButtonPressed = () => _confirmReservation(
dateController.text, timeController.text, attendeeController.text);
buttonTitle = "Make a reservation";
} else if (!reservationsDetailState.isEditing) {
onButtonPressed = () => _setEditMode(!myReservationsDetailState.isEditing);
buttonTitle = "Edit reservation";
} else {
onButtonPressed = () {
if (reservationsDetailState.isEditing) {
_confirmReservation(
dateController.text, timeController.text, attendeeController.text);
} else {
_setEditMode(true);
}
};
buttonTitle = "Confirm reservation";
}
return SizedBox(
child: Button(onPressed: onButtonPressed, title: buttonTitle),
width: double.infinity);
}
它仍然很丑,但缩进明显减少了,在我看来比原来更好。