📖

一日一処: フラグというものの考え方をJS(少しTS)でみていく

2024/02/15に公開

フラグ

フラグとはすなわち、旗だ。旗の上げ下げによって、プログラムは処理を選択する。
たとえば、このようなものだ。

let flag = true
if (flag === true) {
  console.log('on')
} else {
  console.log('off')
}

非常によく見るパターンであり、これと同じコードは世界にどれだけあるのだろうかと考えると砂の粒よりも多いかもしれない。

より適切なパターン

前述のflag変数のパターンは非常に不快だ。この変数名で怒られたことがある人もいるかも知れない。その理由は明確だ。flag=旗。旗?何の旗?ということだ。trueとfalseでそれぞれどういう状態であるかも明確ではない。例えば、trueは真の意味を持っているため、変数名が動詞の過去形であれば、それが事実ということがわかりやすくなる。

let created = true

動詞の過去形の表現にすることで、その内容が真の状態である=行われた事実として把握しやすくなるのだ。逆にfalseの場合は、created(作成された)が、偽=行われていない、と考えることも容易だ。
ただし、過去形であればなんでもいいわけでもない。

let unsupported = true
if (unsupported === false) {
  console.log('what?')
}

英語を母国語にしていた場合でも若干の混乱をきたす。変数名が否定である場合、真のとき、どっちの状態であるか、即座に答えることができない。if文も同じだが、基本的には否定的な表現を組み込まず、肯定的な表現を目指すのが最適だ。「誤っていないもの以外をすべて選べ」のような性格の悪い問題文を思い出す。

フラグの大量生産を抑止

変数名に意味をもたせると、スッキリするが、これはどうだろうか。

let signed = true
let finished = false
let confirmed = true
let clicked = true
if (signed && !finished && confimed && clicked) {
  console.log('crazy')
}

それぞれの意味は理解できるが、果たして、これは適切なパターンなのだろうか。変数単体だと文句はないが、if文をみてほしい。これは流石に初見殺しだ(一例であり意味はない)。ただ、このようなフラグとして扱う変数名はオブジェクトなどを用いて、大量に使用する場合もある。その場合は、このようにするのはいかがだろうか。

let signed = true
let finished = false
let confirmed = true
let clicked = true
const notFinished = signed && !finished
const clickedConfirmation =  confimed && clicked
if (notFinished) {
  if (clickedConfirmation) {
    console.log('continuable!')
  }
}

if文をネストする必要がなければ、このようにしてもいいかもしれない。

let signed = true
let finished = false
let confirmed = true
let clicked = true
const notFinishedAndConfimed = signed && !finished && confimed && clicked
if (notFinishedAndConfimed) {
  console.log('continuable!')
}

このように、状態などに対してしっかりと変数名をつけていくと、他人がみてもわかりやすく、デバッグしやすくなるのではないだろうか。

TypeScriptのユニオンはフラグでも

TypeScriptには、ご存知のユニオン型が存在する。いっそのこと、これでフラグ管理ができるとよりシンプルかもしれない。

type UserStatus = 'signed' | 'confirmed' | 'finished'
let userStatus = 'confirmed'
if (userStatus === 'confirmed') {
  console.log('do you have finished?')
}

ただ、これだと、困ることがある。それは、複数の状態が重なっているときだ。状態が複合せずに、一定の結果のみならこれでも問題ないだろう。ただし、「A」か「B」か「AとB」のようなときにはあまり使えない。

個人的最適解

const Statuses = {
  signed:    0b0001,
  confirmed: 0b0010,
  finished:  0b0100,
}

let userStatus = Statuses.signed
if (userStatus === Statuses.signed) {
  console.log('signed')
}

userStatus += Statuses.confirmed
if (userStatus === Statuses.signed + Statuses.confirmed) {
  console.log('confirmed')
}

userStatus += Statuses.finished
if (userStatus & Statuses.finished) {
  console.log('finished')
}

複合的な状態を管理するのは、こうすると楽かもしれない。それぞれの値を2進数の「フラグ」とした。一番右側がsigned用のフラグというふうに、2進数の桁に合わせて、それぞれのフラグに対応させた。2進数は知っての通り、一桁で0か1の表現しかできない。そして、0は偽で、1は真だ。これによって、管理する変数は減らすことができた。また、if文の条件でも強い能力を発揮する。たとえば、userStatus & Statuses.finishedこの様に書くとAND演算となるため、finishedで設定している2進数の桁以外は点灯しなくなる。否定する場合は、少々表現しにくくなるが、そもそも条件は肯定的に書いたほうが見やすいため、このように記述することで、不意に否定的な条件を書かなくて住む抑止力にもなるかもしれない。

Discussion