JavaScript /Node.JSのコンテキスト内。 noasyncプログラミングが必要な場合、Callback関数を使用するとソースコードの保守性が向上しますか?
たとえば、 プレーンコード は意味的に正確に聞こえ、 2番目のもの その不必要にコールバック関数を使用するよりも、保守/拡張が容易になりますか? ?
平野
var validateId = function ( id ) {
if ( undefined !== id ) {
return true;
} else {
return false;
}
}
var setId = function ( id ) {
if ( true === validateId(id) ) {
userId = id;
console.log( "success" );
} else {
console.log( "failed: ", "invalid id" );
};
}
コールバック済み
var validateId = function ( id, success, fail ) {
if ( undefined !== id ) {
success( id );
} else {
fail( "invalid id" );
}
}
var setId = function ( id ) {
validateId( id, function success (validatedId) {
userId = validatedId;
console.log( "success" );
}, function fail ( error ) {
console.log( "failed: ", error );
});
}
アップデート#1
読みやすく保守しやすいコードの書き方に関する一般的なアドバイスは探していません。最初の行に書かれているように、Callbacksを使用して(どのプログラミング言語でも使用されていませんが、具体的にはJavaScriptで)コードの保守性が向上するかどうかを確認しています。そして、それがソースコードを意味的により正確にするなら? (validateId
を呼び出し、結果がtrue
の場合は、userId
を比較してvalidateId
を呼び出し、userId
をsuccess
)
どちらの解決策も理にかなっています。多くの場合、関数をパラメーターとして使用すると便利です。これにより、あらゆる状況でコールバックを提供する必要があるため、通常、正しいコードを簡単に記述できます。ただし、コールバックを必要とするAPIは、不必要に深いインデントを作成する傾向があります。これは、複数の検証があり、すべてが合格する必要がある場合に、より明白になります。
// simple solution
function validateA(a) { return a !== undefined }
function validateB(b) { return b !== undefined }
function validateC(c) { return c !== undefined }
function frobnicate(a, b, c) {
if (! validateA(a)) {
console.log("failed: ", "invalid a");
return;
}
if (! validateB(b)) {
console.log("failed: ", "invalid b");
return;
}
if (! validateC(c)) {
console.log("failed: ", "invalid c");
return;
}
console.log("success");
}
// naive callback solution
function validateA(a, onSuccess, onError) {
return (a !== undefined) ? onSuccess(a) : onError("invalid a");
}
function validateB(b, onSuccess, onError) {
return (b !== undefined) ? onSuccess(b) : onError("invalid b");
}
function validateC(c, onSuccess, onError) {
return (c !== undefined) ? onSuccess(c) : onError("invalid c");
}
function frobnicate(a, b, c) {
return validateA(a, function successA(a) {
return validateB(b, function successB(b) {
return validateC(c, function successC(c) {
console.log("success");
}, function failC(msg) { console.log("failed: ", msg) });
}, function failB(msg) { console.log("failed: ", msg) });
}, function failA(msg) { console.log("failed: ", msg) });
}
このコールバックベースの検証では、制御フローが理解できず、成功ハンドラーがエラーハンドラーの前に来るため、エラー処理は処理している問題から視覚的に分離されます。これはメンテナンスの妨げになります。関数のコールバックをスラップするだけではスケーリングしません。
これを解決するためのパターンは存在します。たとえば、コールバックベースの検証関数を正しく構成するコンビネータを作成できます。
function multipleValidations(validations, onSuccess) {
function compose(i) {
if (i >= validations.length) {
return onSuccess;
}
var cont = compose(i + 1);
var what = validations[i][0];
var validation = validations[i][1];
var onError = validations[i][2] || function(msg) {};
return function(x) { return validation(what, cont, onError) };
}
return compose(0)();
}
function frobnicate(a, b, c) {
return multipleValidations([
[a, validateA, function failA(msg) { console.log("fail: ", msg) }],
[b, validateB, function failB(msg) { console.log("fail: ", msg) }],
[c, validateC, function failC(msg) { console.log("fail: ", msg) }]
], function success() {
console.log("success");
});
}
さて、検証を使用する方がはるかに優れていますが、multipleValidations
ヘルパーは醜い(そして自明ではなく、維持するのが難しい)です。同じ検証は、各検証がブール値を返す単なる述語であれば、実装がはるかに簡単になります(ただし、検証でエラーメッセージ自体を定義できず、代わりにエラーコールバックに依存する必要がある場合を除きます)。
function multipleValidations(validations, onSuccess) {
for (var i = 0; i < validations.length; i++) {
var what = validations[i][0];
var validate = validations[i][1];
var onError = validations[i][2] || function fail() {};
if (! validate(what)) {
return onError();
}
}
return onSuccess();
}
必要のない場所でコールバックを使用することは、KISS違反(単純で愚かである)だけではありません。コールバックを積極的に邪魔してコードを難読化することがあります。一部のパターンでは、コールバックが必要であり、それらを使用します。 OKがあります。
シンプルなソリューションを選ぶべきもう1つの理由は、検証の両方の表現を簡単に相互に変換できることです。
function predicateToCallback(predicate, errorMessage) {
return function validate(x, onSuccess, onError) {
return (predicate(x)) ? onSuccess(x) : onError(errorMessage);
};
}
function callbackToPredicate(validate) {
return function predicate(x) {
return validate(x, function onSuccess() {
return true;
}, function onError() {
return false;
});
};
}
元のコードでは、コールバックベースのソリューションは値を返さないため、コールバックを述語に変換することはわずかに困難になります。
function callbackToPredicate(validate) {
return function predicate(x) {
val result;
validate(x, function onSuccess() {
result = true;
}, function onError() {
result = false;
});
return result;
};
}
同じ概念のある表現を別の表現に変換するのはとても簡単なので、より単純な表現から始めるべきです。何らかの理由で必要な場合、コールバックベースのソリューションは関数呼び出しだけです。しかし、おそらく、あなたはそれを必要としないでしょう(YAGNI)。