使用一个函数来改变条件内的东西,使条件依赖于顺序,这是不好的做法吗?
Is it bad practice to use a function that changes stuff inside a condition, making the condition order-dependent?
var a = 1;
function myFunction() {
++a;
return true;
}
// Alert pops up.
if (myFunction() && a === 2) {
alert("Hello, world!");
}
// Alert does not pop up.
if (a === 3 && myFunction()) {
alert("Hello, universe!");
}
https://jsfiddle.net/3oda22e4/6/
myFunction
递增一个变量和 returns 一些东西。如果我在包含它递增的变量的 if
语句中使用这样的函数,则条件将取决于顺序。
这样做是好是坏,为什么?
这是不好的做法,原因如下:
代码的可读性远不如结构良好的代码。如果代码稍后由第三方检查,这一点非常重要。
如果以后更改 myfunction
,代码流将完全不可预测,可能需要进行重大文档更新。
小而简单的更改会对代码的执行产生巨大影响。
看起来很业余
如果你不得不问,那可不是什么好习惯。是的,正是出于您提到的原因,这是一种不好的做法:更改逻辑运算的操作数顺序不应影响结果,因此通常应避免条件中的副作用。特别是当它们隐藏在函数中时。
该函数是纯函数(只读取状态并执行一些逻辑)还是改变状态从它的名字中应该很明显。您有多种选择来修复此代码:
将函数调用放在 if
:
之前
function tryChangeA() {
a++;
return true;
}
var ok = tryChangeA();
if (ok && a == 2) … // alternatively: if (a == 2 && ok)
在 if
:
中明确突变
function testSomething(val) {
return true;
}
if (testSomething(++a) && a == 2) …
将逻辑放在被调用的函数中:
function changeAndTest() {
a++;
return a == 2;
}
if (changeAndTest()) …
无论您是否更改条件中使用的变量,条件都取决于顺序。您用作示例的两个 if 语句是不同的,并且无论您是否使用 myFunction() 都会有所不同。它们相当于:
if (myFunction()) {
if (a === 2) {
alert("Hello, world!")
}
}
// Alert does not pop up.
if (a === 3) {
if (myFunction()) {
alert("Hello, universe!")
}
}
在我看来,您代码中的错误做法不是您在条件内更改条件的操作数值,而是您的应用程序状态在甚至不接受 this 的函数内公开和操作状态变化变量作为参数。我们通常会尝试将函数与其范围之外的代码隔离开来,并使用它们的 return 值来影响其余代码。全局变量在 90% 的情况下都是一个坏主意,并且随着您的代码库变得越来越大,它们往往会产生难以跟踪、调试和解决的问题。
该代码实际上提出了不止一种不良做法:
var a = 1;
function myFunction() {
++a; // 1
return true;
}
if (myFunction() && a === 2) { // 2, 3, 4
alert("Hello, world!")
}
if (a === 3 && myFunction()) { // 2, 3, 4
alert("Hello, universe!")
}
在不同的范围内改变一个变量。这可能是也可能不是问题,但通常是。
在 if
语句条件内调用函数。
这本身不会引起问题,但它并不真正干净。
更好的做法是将该函数的结果分配给一个变量,可能具有描述性名称。这将帮助阅读代码的人了解您想要在 if
语句中检查的确切内容。顺便说一下,函数总是 return true
.
使用一些幻数。
想象一下其他人正在阅读该代码,并且它是大型代码库的一部分。这些数字是什么意思?更好的解决方案是用命名良好的常量替换它们。
如果要支持更多的消息,需要添加更多的条件。
更好的方法是使其可配置。
我会重写代码如下:
const ALERT_CONDITIONS = { // 4
WORLD_MENACE: 2,
UNIVERSE_MENACE: 3,
};
const alertsList = [
{
message: 'Hello world',
condition: ALERT_CONDITIONS.WORLD_MENACE,
},
{
message: 'Hello universe',
condition: ALERT_CONDITIONS.UNIVERSE_MENACE,
},
];
class AlertManager {
constructor(config, defaultMessage) {
this.counter = 0; // 1
this.config = config; // 2
this.defaultMessage = defaultMessage;
}
incrementCounter() {
this.counter++;
}
showAlert() {
this.incrementCounter();
let customMessageBroadcasted = false;
this.config.forEach(entry => { //2
if (entry.condition === this.counter) {
console.log(entry.message);
customMessageBroadcasted = true; // 3
}
});
if (!customMessageBroadcasted) {
console.log(this.defaultMessage)
}
}
}
const alertManager = new AlertManager(alertsList, 'Nothing to alert');
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
一个 class 具有精确的函数,它使用自己的内部状态,而不是一堆依赖于可能位于任何地方的变量的函数。是否使用 class 是一个选择问题。它可以用不同的方式来完成。
使用配置。这意味着如果您想添加更多消息,您根本不必触摸代码。例如,假设该配置来自数据库。
您可能会注意到,这会在函数的外部范围内改变一个变量,但在这种情况下它不会导致任何问题。
使用名称清晰的常量。 (好吧,它可能会更好,但请耐心等待我举个例子)。
MyFunction
违反了一个叫做Tell, Don't Ask的原则。
MyFunction
改变某物的状态,从而使它成为一个命令。如果 MyFunction
成功或以某种方式未能增加 a
,它不应该 return 真或假。它被赋予了一份工作,它必须尝试成功,或者如果它发现目前无法完成工作,它应该抛出异常。
在 if 语句的谓词中,MyFunction
用作查询。
一般来说,查询不应该表现出副作用(即不改变可以观察到的东西)。一个好的查询可以被视为一个计算,因为对于相同的输入,它应该产生相同的输出(有时被描述为 "idempotent")。
知道这些是帮助您和其他人推理代码的指导方针也很重要。 可以导致混淆的代码,将。代码混乱是错误的温床。
有一些很好的模式,例如 Trier-Doer 模式,可以像您的代码示例一样使用,但阅读它的每个人都必须通过名称和结构了解发生了什么。
一个改变东西的函数。什么世界也来了?此函数必须在每次调用时更改内容和 return 不同的值。
考虑一副扑克牌的 dealCard 函数。它发牌 1-52。每次调用它都应该 return 不同的值。
function dealCard() {
++a;
return cards(a);
}
/* 我们假设阵列卡已洗牌 */
/* 为了简洁起见,我们假设牌组是无限的并且不会在 52 处循环*/
var a = 1;
function myFunction() {
++a;
return true;
}
// Alert pops up.
if (myFunction() && a === 2) {
alert("Hello, world!");
}
// Alert does not pop up.
if (a === 3 && myFunction()) {
alert("Hello, universe!");
}
https://jsfiddle.net/3oda22e4/6/
myFunction
递增一个变量和 returns 一些东西。如果我在包含它递增的变量的 if
语句中使用这样的函数,则条件将取决于顺序。
这样做是好是坏,为什么?
这是不好的做法,原因如下:
代码的可读性远不如结构良好的代码。如果代码稍后由第三方检查,这一点非常重要。
如果以后更改
myfunction
,代码流将完全不可预测,可能需要进行重大文档更新。小而简单的更改会对代码的执行产生巨大影响。
看起来很业余
如果你不得不问,那可不是什么好习惯。是的,正是出于您提到的原因,这是一种不好的做法:更改逻辑运算的操作数顺序不应影响结果,因此通常应避免条件中的副作用。特别是当它们隐藏在函数中时。
该函数是纯函数(只读取状态并执行一些逻辑)还是改变状态从它的名字中应该很明显。您有多种选择来修复此代码:
将函数调用放在
之前if
:function tryChangeA() { a++; return true; } var ok = tryChangeA(); if (ok && a == 2) … // alternatively: if (a == 2 && ok)
在
中明确突变if
:function testSomething(val) { return true; } if (testSomething(++a) && a == 2) …
将逻辑放在被调用的函数中:
function changeAndTest() { a++; return a == 2; } if (changeAndTest()) …
无论您是否更改条件中使用的变量,条件都取决于顺序。您用作示例的两个 if 语句是不同的,并且无论您是否使用 myFunction() 都会有所不同。它们相当于:
if (myFunction()) {
if (a === 2) {
alert("Hello, world!")
}
}
// Alert does not pop up.
if (a === 3) {
if (myFunction()) {
alert("Hello, universe!")
}
}
在我看来,您代码中的错误做法不是您在条件内更改条件的操作数值,而是您的应用程序状态在甚至不接受 this 的函数内公开和操作状态变化变量作为参数。我们通常会尝试将函数与其范围之外的代码隔离开来,并使用它们的 return 值来影响其余代码。全局变量在 90% 的情况下都是一个坏主意,并且随着您的代码库变得越来越大,它们往往会产生难以跟踪、调试和解决的问题。
该代码实际上提出了不止一种不良做法:
var a = 1;
function myFunction() {
++a; // 1
return true;
}
if (myFunction() && a === 2) { // 2, 3, 4
alert("Hello, world!")
}
if (a === 3 && myFunction()) { // 2, 3, 4
alert("Hello, universe!")
}
在不同的范围内改变一个变量。这可能是也可能不是问题,但通常是。
在
if
语句条件内调用函数。 这本身不会引起问题,但它并不真正干净。 更好的做法是将该函数的结果分配给一个变量,可能具有描述性名称。这将帮助阅读代码的人了解您想要在if
语句中检查的确切内容。顺便说一下,函数总是 returntrue
.使用一些幻数。 想象一下其他人正在阅读该代码,并且它是大型代码库的一部分。这些数字是什么意思?更好的解决方案是用命名良好的常量替换它们。
如果要支持更多的消息,需要添加更多的条件。 更好的方法是使其可配置。
我会重写代码如下:
const ALERT_CONDITIONS = { // 4
WORLD_MENACE: 2,
UNIVERSE_MENACE: 3,
};
const alertsList = [
{
message: 'Hello world',
condition: ALERT_CONDITIONS.WORLD_MENACE,
},
{
message: 'Hello universe',
condition: ALERT_CONDITIONS.UNIVERSE_MENACE,
},
];
class AlertManager {
constructor(config, defaultMessage) {
this.counter = 0; // 1
this.config = config; // 2
this.defaultMessage = defaultMessage;
}
incrementCounter() {
this.counter++;
}
showAlert() {
this.incrementCounter();
let customMessageBroadcasted = false;
this.config.forEach(entry => { //2
if (entry.condition === this.counter) {
console.log(entry.message);
customMessageBroadcasted = true; // 3
}
});
if (!customMessageBroadcasted) {
console.log(this.defaultMessage)
}
}
}
const alertManager = new AlertManager(alertsList, 'Nothing to alert');
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
一个 class 具有精确的函数,它使用自己的内部状态,而不是一堆依赖于可能位于任何地方的变量的函数。是否使用 class 是一个选择问题。它可以用不同的方式来完成。
使用配置。这意味着如果您想添加更多消息,您根本不必触摸代码。例如,假设该配置来自数据库。
您可能会注意到,这会在函数的外部范围内改变一个变量,但在这种情况下它不会导致任何问题。
使用名称清晰的常量。 (好吧,它可能会更好,但请耐心等待我举个例子)。
MyFunction
违反了一个叫做Tell, Don't Ask的原则。
MyFunction
改变某物的状态,从而使它成为一个命令。如果 MyFunction
成功或以某种方式未能增加 a
,它不应该 return 真或假。它被赋予了一份工作,它必须尝试成功,或者如果它发现目前无法完成工作,它应该抛出异常。
在 if 语句的谓词中,MyFunction
用作查询。
一般来说,查询不应该表现出副作用(即不改变可以观察到的东西)。一个好的查询可以被视为一个计算,因为对于相同的输入,它应该产生相同的输出(有时被描述为 "idempotent")。
知道这些是帮助您和其他人推理代码的指导方针也很重要。 可以导致混淆的代码,将。代码混乱是错误的温床。
有一些很好的模式,例如 Trier-Doer 模式,可以像您的代码示例一样使用,但阅读它的每个人都必须通过名称和结构了解发生了什么。
一个改变东西的函数。什么世界也来了?此函数必须在每次调用时更改内容和 return 不同的值。
考虑一副扑克牌的 dealCard 函数。它发牌 1-52。每次调用它都应该 return 不同的值。
function dealCard() {
++a;
return cards(a);
}
/* 我们假设阵列卡已洗牌 */
/* 为了简洁起见,我们假设牌组是无限的并且不会在 52 处循环*/