S-JIS[2008-05-02/2010-07-23] 変更履歴

プログラミングの定石(私見)

プログラミング言語も「言語」なので、言葉として理解し易いように書くべきと考えます。
(そうして書かれたプログラムは、素直に言葉(文章)として読むことができます)


条件判断

プログラミングに欠かせないのが条件判断による分岐。
どのコンピューター言語にも「IF文」と呼ばれるもの(に該当するもの)が必ず有る。
条件を満たしていれば「THEN部」、満たしていなければ「ELSE部」が処理される。

ただ、ELSE部が無い言語はあるけど(アセンブリ言語とか)。


真偽値

条件判断結果は真(しん:true)か偽(ぎ:false)で表す。
この辺りは、理論的には数学の一分野『ブール代数』で扱っている。

コンピューター言語によっては、真偽値(しんぎち)をbool・booleanといった型で表す(PascalやJava等)。この型は真と偽の二値しか持たない。
そういう型を持っていない言語でも、「0を偽、それ以外を真として扱う」などの決まりを持っている(C言語やBASIC等)。
つまり論理的には(=どの言語であろうが)、真と偽の二種類しか値を持たない。
コンピューター言語毎の真偽値

	IF (条件) THEN{ 条件を満たしたときの処理 } ELSE{ 条件を満たさなかったときの処理 }

「条件」に当たる部分は、必ず真偽値となる。
それは、例えば「数値かどうか」とか「成人かどうか(年齢が20歳以上か)」とか「処理が成功したかどうか」とかの疑問文 で、答が「はい(真)」か「いいえ(偽)」で表せるものとなる。


真偽値型の変数(関数)との比較はNG!

IF文は普通の自然言語(特に英語)に近い状態に直せる。

	IF (条件) THEN{  } ELSE{  }
	if satisfied condition, then , else .
	もし条件を満たしたら、そうでなければ

この英語が変なのは、ひしだまに英語力が皆無な所為です><

したがって、真偽値を表す型の変数(あるいは真偽値を返す関数)に対し、その値が「真と等しい」あるいは「偽と等しくない」という聞き方をするのは相当の変人だと思う。
「あなたは20歳以上ですか?」という質問を「あなたは20歳以上ですかという質問の答がYesですか?」と聞く人はいないでしょう?^^;
なのにプログラミングでは、そういう事を書く人がいる。

	boolean b = あなたは20歳以上ですか?(Yesならtrue、Noならfalse)
	if (b=true)  then{ 成人です }
	if (b≠true)  then{ 子供です }
	if (b=false) then{ 子供です }

このような書き方は(大抵の場合)コンピューター言語の文法上許されているけれど、真偽値の意味(使用目的・存在理由)が分かっていない証拠じゃないかなぁ。

真偽値の変数(や関数)であれば、それは(真偽値の定数との比較などせずに)そのままIF文の条件に書くべき。

	boolean b = あなたは20歳以上ですか?(Yesならtrue、Noならfalse)
	if (b)     then{ 成人です }
	if (not b) then{ 子供です }

コンピューター言語毎の否定方法(not)

ただし、この例のように変数名が「b」というような無意味な名前では、それが何を意味しているのか分からなさすぎる(苦笑)(そんな変数名だからtrueやfalseと比較したくなるのかもしれない。真偽値でない数値とかならば、定数値と比較するのは当たり前だし)
変数名は、意味が分かるように付けるべき。(大昔のBASICのように、変数名に2文字しか使えないなら無理だが)

	boolean isAdult = あなたは20歳以上ですか?(Yesならtrue、Noならfalse)
	if (isAdult)     then{ 成人です }
	if (not isAdult) then{ 子供です }

「if (isAdult)」→「成人ならば」
「if (not isAdult)」→「成人でないならば」
「if (isAdult=true)」→「成人かどうかがtrueならば」…ってのはくどいでしょ?
もしくは「isAdultの値がtrueならば」…このIF文(あるいはisAdultという変数)の目的(仕様)が理解できているとは思えないよねぇ。
(booleanでない型で「if (code=10)」となっていて、「10って何を意味しているの?」と聞かれて「知りません。設計書にそういう値が書いてありました」って言うようなもの?
「isAdultって何を意味してるの?」「知りません。booleanだからtrueと比較してみました」…うーん、ちょっと例えている内容が違う?)

同様に、「処理結果が正常」という判断をしたいときに、変数名にresultとか付けるのも意味不明。

	boolean result = 何らかの処理結果
	if (result) then{ 祝いましょう } else{ 泣きましょう }

→「もし結果だったら祝いましょう、それ以外だったら泣きましょう」…変な日本語〜。

	boolean success = 何らかの処理結果
	if (success) then{ 祝いましょう } else{ 泣きましょう }

→「もし成功だったら祝いましょう、それ以外だったら泣きましょう」

「isAdult」や「success」が良い変数名の例かというと自分でもかなり疑問なんですが(汗)、少なくとも無意味・無関係な名称よりは良いかと。

もうひとつ、昔聞いた話があった。[2010-07-23]
booleanの値を「=」や「≠」で比較すると、結果はbooleanになる。booleanの値が真かどうかを判定するのに「=」「≠」を使うならば、比較結果に対しても「=true」(あるいは「≠false」)と書くべきじゃないの?
というもの。つまり、以下のようになる。

	if (b=true) 〜
↓
	if ((b=true)=true) 〜
↓
	if (((b=true)=true)=true) 〜

再帰的に無限に続く(笑)


等値判断の書き順

コンピューター言語にとって、以下の2つは(たいてい)全く同じ意味(等価)である。

	if (変数=123) then{ 〜 }
	if (123=変数) then{ 〜 }

しかし、(基本的には)前者が良い。

IF文は普通の自然言語に近い状態に直せる。上の2つは、以下のように訳すことになる。

	もし変数が123(と等しい)ならば、〜
	もし123が変数(と等しい)ならば、〜

普段の会話(思考)で後者のような考え方をしている人はまずいないだろう。
(「年齢が20歳なら」という文章が「20歳が年齢なら」だったら、僕には理解できないっす…)

まして、複数の数値と等しいことを示したい場合、日本語ではこう言う。
「もし変数が123, 456, 789のいずれかと等しいならば、〜」

「もし変数が123と等しいか、456と等しいか、789と等しいならば、〜」

SQLのような「in」という演算子があれば、これは「変数 in (123, 456, 789)」と書くところだ。
そういう演算子が無い場合は「変数=123 or 変数=456 or 変数=789」と書くだろう。
「123=変数 or 456=変数 or 789=変数」と書かれているのを見たら、ひしだまの様に複雑なものに対処できない脳みそでは何が言いたいのかすぐには理解できない。
(慣れれば自動的に頭の中で変換されるというのはあるけど)

と、以上が一般論。
C言語においては、「数値=変数」と書く書き方には意義がある
しかしJavaにおいては意味は無い


大小判断の書き順

等値判断の書き方と同様、大小比較も自然言語(仕様)の書き順に近づけた方がよい。

自然言語(数学の表現) コンピューター言語
変数<値 変数<値
変数>値 変数>値
値<変数 値<変数
最小値<変数<最大値 最小値<変数 かつ 変数<最大値

「変数を左側に書く」という考えに囚われると「変数>最小値 かつ 変数<最大値」になってしまうが、これは却って分かりにくい。
地球人の数学的常識では、左の方が小さくて右の方が大きい、すなわち順序の向きがそうなっている。なのに変数を常に左側に書いてしまうと、その順序が崩れて思考の妨げになる。
ただ、せめて2行にしてインデントを合わせるなら、まだ許せるかも。

	変数>最小値 かつ
	変数<最大値

また、仕様(実現したい内容の考え方)次第では、「最大値>変数>最小値」のようになる事もあるだろう。
その場合は、プログラミングするときも仕様と同じ順序で書く方が素直で分かり易い。


計算式

言語やライブラリーによって提供されていない機能(計算式)がある場合に、それを代替する手段(定石)がある。[2009-05-10]
とは言ってもここで書いているのはかなり技巧的なので、使わないならそれに越したことは無い(苦笑)
最近の言語では目的に応じた関数が提供されていることが多いので、そういうものがあるならそれを使うべき。

四捨五入

0.5を足して切り捨てると四捨五入になる(正の数の場合)。[2009-05-10]
ただ、一般に小数を扱う際に使われる浮動小数点は十進数の小数を正確に表せるとは限らない為、言語やライブラリーによって四捨五入の関数が提供されているなら、それを使う方がいい。

各言語の四捨五入の関数


切り上げ

小数点1桁の場合、0.9を足して切り捨てると切り上げになる(正の数の場合)。[2009-05-10]
(浮動小数点に関しては四捨五入と同じ注意が必要)

各言語の切り上げの関数


例えば40行で1ページになる場合に100行のデータは3ページとなる。80行なら2ページ、81行なら3ページ。0行なら0ページ、1行なら1ページ。
これを汎用的に「1ページ当たりn行」の場合に何ページになるか求めるには、以下のように計算できる。

ページ数 = (全行数 + (n−1)) ÷ n	(小数以下切り捨て)

(100 + 40-1) / 40 = 139 / 40 = 3
( 80 + 40-1) / 40 = 119 / 40 = 2
( 81 + 40-1) / 40 = 120 / 40 = 3
(  0 + 40-1) / 40 =  39 / 40 = 0
(  1 + 40-1) / 40 =  40 / 40 = 1

世紀

西暦から世紀を算出したい場合、以下の式で計算できる(紀元後のみ)。[2009-05-10]

世紀 = (西暦 + 99) ÷ 100	(小数以下切り捨て)

西暦2001年は(2001+99)÷100=2100÷100=21世紀、西暦2000年は2099÷100で20世紀。(20世紀末は2000年であって、1999年ではない。ましてや1999年は19世紀ではない)
西暦1年は(1+99)÷100=100÷100で1世紀。


ローテート

一連の番号で順序的な関係を持つ位置を表し、1ずつ増やしたり減らしたりすることによって位置を変えるようなデータを考えることがある。[2009-05-10]
この場合、先頭の数より小さくなったら最大の数字にし、最大の数値より大きくなったら最小の数に変換するという考慮が必要になる。

例えば45度ずつの方角(全部で8方向で、値は0〜7)を割り当て、向きを表すことを考える。

7 0 1
6   2
5 4 3

4が南向きで、右に45度方向を変える場合は1足して5(南西)、左に変える場合は1引いて3(南東)になる。
北向きは0で、右に変える場合は同じく1を足して1(北東)にできるが、左に変える場合は1引いて-1になるので、それは7にする必要がある。
同じく、方角7から1足したら8になってしまうので、0に変換する必要がある。
これを素直にコーディングするとIF文が出てきてしまうが、以下のような計算式にすればIF文は不要で、1つの式だけで計算できる。

z = 右に変える場合は+1、左に変える場合は-1
新方角 = (現方角 + z + 8) mod 8

「mod 8」は、8で割った余り(剰余)を意味する。→各言語の剰余演算
したがって、8で割った余りは必ず0〜7になるので、使用したい値の範囲に収まる。

負の数は扱いたくないので、“割られる数”に8を足している。こうすれば、0から1を引く場合の「(0-1+8) mod 8」は「7 mod 8」となり、結果は7で、期待通り。
“余り”だけ考える場合には“割る数”の倍数はいくつ足しても無関係な為、問題ない。
7に1足す場合も「(7+1+8) mod 8」で「16 mod 8」は0となり、期待通り。

ちょっとした変形として、以下のような計算式でも同じ。

z = 右に変える場合は1、左に変える場合は7(8 - 1)
新方角 = (現方角 + z) mod 8

Javaでラジオボタンをグルーピングしてカーソルキーで移動する例


ちょっと話は逸れるが、こういった数値を扱う場合、コーディング上は定数化するのが常識。

Const 北 = 0, 北東 = 1, …, 北西 = 7
Const 方角数 = 8
Const 右旋回増分 = +1, 左旋回増分 = -1

z = 右に変える場合は「右旋回増分」、左に変える場合は「左旋回増分」
新方角 = (現方角 + z + 方角数) mod 方角数
Const 右旋回増分 = 1, 左旋回増分 = 方角数-1

z = 右に変える場合は「右旋回増分」、左に変える場合は「左旋回増分」
新方角 = (現方角 + z) mod 方角数

後者のロジックでは「北西」と「左旋回増分」が同じ7という値になるが、意味は異なる。こういう場合は当然別の定数名を割り当て、使用したい箇所では意味の通じる方を使う。

もっと言えば、「方角を変更する」という関数を1つ作り、それを呼び出すようにした方が、式を直接書くよりもコーディングはすっきりする。
また、ロジックを変更したい場合も(大掛かりな変更でなければ)関数内を書き換えるだけで済む。
何箇所かで使う場合、単純な式だからと言ってコピペせず、同じ意味のロジックは1つの関数にしてそれを呼び出すようにすべき。


二択

C言語の場合、条件演算子「?:」というものがある。[2009-05-10]
「条件 ? 値1 : 値2」という書き方をし、条件が満たされる場合は値1、そうでない場合は値2になる。

r = (b ≠ 0) ? 100 : 300	…bが0以外ならrは100、そうでないならrは300になる

各言語の条件演算子

BASICの場合、真偽値は真の場合-1、偽の場合は0となるので、それを使って以下のような計算式が書ける。

r =  300 - (100 - 300) × (b<>0)
bが0以外の場合は(b<>0)が-1になるので、300-(100-300)×(-1) = 300+100-300 = 100
bが0の場合は    (b<>0)が 0になるので、300-(100-300)×0    = 300

定数部分を計算しておくことも出来るが、「r = 300 + 200×(b<>0)」となって、何をしたいのか一見よく分からなくなってくる(爆)
まぁそうでなくても微妙に分かりづらいので、現在では使わない方がいいでしょう。
(そもそも最近の言語では、真偽値をそのまま整数として扱うことが出来ない)


コンピューター用語へ戻る / 技術メモへ戻る
メールの送信先:ひしだま