私は次を簡素化しようとしています:
function handleDirection(src) {
if (src === 'left') {
if (inverse) {
tracker--;
} else {
tracker++;
}
} else {
if (inverse) {
tracker++;
} else {
tracker--;
}
}
}
条件の数を減らします。 src
は常に'left'
または'right'
になります。
最初のチェックの結果で確認できます。
これは排他的なORチェックです。
// typeof inverse === 'boolean'
function handleDirection(src) {
if (src === 'left' === inverse) {
tracker--;
} else {
tracker++;
}
}
このチェックは、次の順序で式を評価します(src === 'left') === inverse
:
src === 'left' === inverse
---- first --- returns a boolean value
--------- second --------- take result of former check & compairs it with another boolean
function handleDirection(src) {
var movement = 1;
if(src === 'left')
movement = -1;
if(inverse)
tracker += movement;
else
tracker -= movement;
}
1行のコードでそれを行うこともできます。
function getDirectionOffset(src) {
tracker += (src === 'left' ? 1 : -1) * (inverse ? -1 : 1);
}
これは、状態に応じて1
または-1
を返す3項式に単純化できます。次に、それをtracker
に追加するだけです。
function handleDirection(src) {
var delta = (src === 'left' && inverse) || (src !== 'left' && !inverse) ? -1 : 1;
tracker += delta;
}
@NinaScholzが彼女の答えで指摘したロジックを使用して、これをさらに簡略化できます。
function handleDirection(src) {
var delta = (src === 'left') === inverse ? -1 : 1;
tracker += delta;
}
inverse
が一度設定されたフラグである場合、毎回考慮する必要はなく、その影響を計算できますonceそのまま使用できます。コードブランチとロジックを削減します。作業中に変更したい場合は、再利用するために計算のロジックを分離する必要があります。
また、移動方向を自己完結型関数に抽出すると、handleDirection
が非常に単純になります。src
とinvert
に基づいて、移動する方向を計算します。
let tracker = 0;
//extract logic for the movement offset based on direction
function getDirectionOffset(src) {
return src === 'left' ? 1 : -1;
}
//have a setter for the invert property
function setInverse(isInverse) {
movementModifier = isInverse ? -1 : 1
}
//declare the variable dependent on the inverse property
let movementModifier;
//initialise movementModifier variable
setInverse(false);
function handleDirection(src) {
const offset = getDirectionOffset(src) * movementModifier;
tracker += offset;
}
// usage
setInverse(true);
handleDirection("left");
handleDirection("left");
handleDirection("right");
console.log(tracker);
とはいえ、これはすべて、関数を使用しないか、異なる方法で使用する必要があることを示唆しています。クラス内のすべての機能を収集するか、関数の周りにすべての情報を渡すことができるため、グローバルはありません。次に、概念のオブジェクト指向実装のサンプルを示します。
class TrackerMover {
constructor(inverse) {
this.tracker = 0;
this.movementModifier = inverse ? 1 : -1
}
handleDirection(src) {
const offset = this.getDirectionOffset(src) * this.movementModifier;
this.tracker += offset;
}
getDirectionOffset(src) {
return src === 'left' ? -1 : 1;
}
getPosition() {
return this.tracker;
}
}
//usage
const mover = new TrackerMover(true);
mover.handleDirection("left");
mover.handleDirection("left");
mover.handleDirection("right");
console.log(mover.getPosition())
ところで、別の選択肢は、毎回動きを計算しないことです。実際に毎回何が起こっているかを知っています。実際には、入力がsrc === left
およびinverse
であり、出力が追跡を修正する方法である真理値表があります。
+--------+------------+--------+
| isLeft | isInverted | Offset |
+--------+------------+--------+
| true | true | -1 |
| true | false | 1 |
| false | true | 1 |
| false | false | -1 |
+--------+------------+--------+
だから、あなたはちょうどそのテーブルを置くことができます。
let tracker = 0;
let invert = false;
const movementLookupTable = {
"true": { },
"false": { },
}
//it can be initialised as part of the above expression but this is more readable
movementLookupTable[true ][true ] = -1;
movementLookupTable[true ][false] = 1;
movementLookupTable[false][true ] = 1;
movementLookupTable[false][false] = -1;
function handleDirection(src) {
const offset = movementLookupTable[src === "left"][invert];
tracker += offset;
}
// usage
invert = true;
handleDirection("left");
handleDirection("left");
handleDirection("right");
console.log(tracker);
この場合、やり過ぎかもしれませんが、より多くのフラグ(フラグのvaluesを含む)や終了状態がさらにある場合、このアプローチは便利です。たとえば、4つの方向を紹介したいが、tracker
またはup
の場合は、down
の値を変更しないでください。
+-----------+------------+--------+
| direction | isInverted | Offset |
+-----------+------------+--------+
| left | true | -1 |
| left | false | 1 |
| right | true | 1 |
| right | false | -1 |
| up | false | 0 |
| up | true | 0 |
| down | false | 0 |
| down | true | 0 |
+-----------+------------+--------+
ご覧のとおり、今では単なるブール値ではなく、任意の値を処理できます。また、テーブルを使用して、invert
をwindDirection
のようなものに変更します。したがって、動きがleft
で、windDirection
がright
の場合、結果は今のようになりますが、left
の方向とleft
の風を送ることができるので、さらにに移動します。または、up
を移動でき、風向はleft
なので、tracker
(この時点でX座標)は実際に変更されます。
+-----------+---------------+---------+
| direction | windDirection | OffsetX |
+-----------+---------------+---------+
| left | right | -1 |
| left | up | 1 |
| left | down | 1 |
| left | left | 2 |
| right | up | -1 |
| right | down | -1 |
| right | right | -2 |
| right | left | 1 |
| up | up | 0 |
| up | down | 0 |
| up | left | 1 |
| up | right | -1 |
| down | up | 0 |
| down | down | 0 |
| down | left | 1 |
| down | right | -1 |
+-----------+---------------+---------+
4つの方向と4つの風の方向を考慮すると、ロジックは将来の読み取りと保守の両方に非常に迷惑になる可能性がありますが、ルックアップテーブルのみがある場合は簡単で、これを簡単に拡張して対角線を処理することもできます(仮定しましょう)それらは0.5
ではなく1
によって値を変更し、テーブルから値を取得する限り、アルゴリズムは実際には気にしません。
if
文はまったく必要ありません。 srcおよびinverseに応じて正または負の増分を三項演算子を使用して計算することにより、同じ操作を実行できます。
function handleDirection(src) {
tracker += (src == "left" ? 1 : -1) * (inverse ? -1 : 1);
};
ところで効率を上げるために、デコードに余分な処理が必要な文字列ではなく、数値の増分/減分を直接使用することをお勧めします。定数を使用して同じ読みやすさを実現できます。
また、inverseは、1(反転しない)と-1(反転する)の間で切り替わる数値として最適化できます。
const left = 1;
const right = -1;
var direction = 1;
function handleDirection(src) {
tracker += src * direction;
}
function reverse() { // (Example)
direction = direction * -1;
}
...「右」と「左」のキーワードが何らかのテキストユーザー入力に由来する場合でも、辞書から簡単に翻訳できます。
const steps = {
left = 1;
right = -1;
};
function handleDirection(src) {
tracker += steps[src] * direction;
}
src == left
またはinverse
の一方がtrueで、もう一方がtrueではない場合はトラッカーを増やし、それ以外の場合はそれを減らします。これが「XOR」^
演算子の動作です。
function handleDirection(src) {
if (src === 'left' ^ inverse) {
tracker++;
} else {
tracker--;
}
}
三項式を使用することでそれをさらに減らすことができます:
function handleDirection(src) {
tracker += src === 'left' ^ inverse ? 1 : -1;
}
または、暗黙的なキャストと「巧妙な」算術を使用して、あらゆる種類の条件付き式を回避したい場合:
function handleDirection(src) {
tracker += 1 - 2 * (src === 'right' ^ inverse); // either 1-0=1 or 1-2=-1
}
これには条件が1つしかないため、他の答えよりも直感的に読み取れます。
function handleDirection(src) {
if (
((src === 'left') && !inverse) ||
((src === 'right') && inverse)
) {
tracker++;
}
else {
tracker--;
}
}
私は他の人が嫌いで、可能であればネストを避けようとします。これは、inverse
の考えをより自然な方法で伝えていると思います。
function handleDirection(src)
{
let change = 1;
if ('right' == src)
change = -1;
if (inverse)
change = -change;
tracker += change;
}
短絡構文または三項演算子を使用できます
// by using short circuiting
function handleDirection(src) {
if (src == 'left') tracker = inverse && tracker-1 || tracker +1
else tracker = inverse && tracker+1 || tracker -1
}
// by using ternary operator
function handleDirection(src) {
if (src == 'left') tracker = inverse ? tracker-1 : tracker +1
else tracker = inverse ? tracker+1 : tracker -1
}
これは直接的な「単純化」で質問に直接対処するものではないことを知っていますが、コードを読みやすくすると同時にコード品質のいくつかの問題に対処する回答を提供したいと思います。
副作用について
まず、この特定の関数は外部値を変更します。これにより、 副作用 の問題が発生します。
また、テストを実行するために最初に「状態環境を作成する」必要があるため、このような機能をテストすることははるかに困難です。
最初の簡単な調整は、すべての外部値をパラメーターで受け入れ、任意の(あなたの場合はtracker
)に割り当てられた1
または-1
値を返すことです。
文字列を含む排他的または条件付き
第二に、排他的または文字列値にif/else
を使用すると、src
が'right'
以外のものになる可能性がある未定義の状態になりますが、関数は'right'
のように動作します。代わりに、例外をスローする必要があります。ここでは、スイッチを使用すると便利です。
これらのポイントを関数に適用する
上記の点を考慮すると、全体的な機能は次のようになります。
function handleDirection (src, inverse) {
switch (src) {
case 'left':
return inverse ? -1 : 1
case 'right':
return inverse ? 1 : -1
default:
throw new Error(`Unknown src: ${src}`)
}
}
この関数を簡単にテストできます:
handleDirection('left' , true) // -1
handleDirection('left' , false) // 1
handleDirection('right', true) // 1
handleDirection('right', false) // -1
handleDirection('middle',true) // Error: Unknown src: middle
これで、関数はtracker
(リファクタリングするときの貴重な時間を考えてください)から明確に切り離されましたが、さらに関数が何をするかは完全に明確です。
注
私が強調したいのは、最小限の行で最も単純なコードを書くことではなく、読み取り/理解および維持します。提供されるソリューションの多くほど短くはありませんが、誰もがすぐにそれが何をするかを理解する必要があります。
現在、文字列を比較していますが、これはお勧めしません。たとえば、「left」の代わりに「Left」を使用すると、最初のifステートメントが失敗します。おそらくブール値は2つの状態のみを保証できるため、ここで使用できます。
内部のif文は 条件演算子 で圧縮できます。
おそらくこのようなものがあなたが探しているものです:
function handleDirection(src) {
if (src) {
inverse ? tracker-- : tracker++;
} else {
inverse ? tracker++ : tracker--;
}
}