聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘...

17
",........-- <miyaden t@ ane t . ne.jp> C MAGAZINE 2005 7 42

Upload: others

Post on 13-Jul-2020

5 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

",........--

a・・‘

宮坂電人<[email protected]>

ソースの悪い部分がどうしても自につくようになります。

でも、「他人のソースが汚くてわかりにくしリと思ってい

るあなた自身のソースは本当に大丈夫でしょうか? 知

らないうちに、理解しにくくて、修正しにくくて、メン

テナンスや機能拡張が困難となる危険なコーテ‘イングを

行う癖がついているかもしれません。本特集では開発現

場でよく見かける「汚いソース」や「へたなコーデイングJ

の例を観察し、ダメプログラムにはどのようなパターン

があるか、あるいは万が一ダメプログラムに遭遇したと

きにどう対処すればいいかを考察します。

読者のみなさんは、他人が書いたソースコードを見て、

その読みにくさに驚いたことはありませんか? あるい

は、友人から「ソースを見せてくれJといわれたときに「汚

いから勘弁して」と断った経験はありませんか? アマ

チユアプロク.ラマとしてプログラムを書いている聞は、

ソースをどうしても公開しなくてはならないということまれ

は稀ですし、プログラムさえ正しく動作していればソー

スが汚くても苦情が出るとともほとんどありません。し

かし、職業プログラマになると、多くのソースを読み、

修正し、メンテナンスする機会が多くなるので、他人の

a・・‘

残念ながらアマチュアなのは、そういうこ

とをいっている人のほうなのですっ「動けば

正解Jというのは、一見正しいように思えま

すが、のちのちのことを考えると動くだけ

では十分ではないことは容易に推~1lIjできま

す。たとえば、プログラムのパージョンア

と驚くほど壮絶な汚さのコーデイングがな

されているケースをよく見かけます。そし

て、親切にそれを指摘すると、「プログラム

なんて動けばいいんだよ、ソースが美しい

とか汚いとかいっているのはアマチュアだ

ね」と反論されることがあります。しかし、

一一三設会一三、のり

ム小山手一らもや

いふUoJJがるり

71て事いこ

一一一千、件以

tJ仕庁、ど

主浅い前ー川動弘

正芳二五話会一イ?とにイ叶

J・.0・吋叫ん一一マり幻

一ーに一Nu--ラおむ

'~川町4・一グど凪町

長~辺誠一訟ロ待日目

一一主一一一」三一」一プ期ル

一一日夜{‘九一

a

業は一

与が呪井県二職ムソ

JM.

はラ

η

11M,・以者グめ

よ→相マ一\一筆ロ、

ιllEEBプの

C MAGAZINE 2005 7 42

Page 2: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

'園田園Fー←

~ I ップを行う場合やパグが見つかったときに、汚いソースはたちまち障害となってプログ

ラマの足をひっぱったり、いつまでたって

も仕事が終わらない泥沼の世界にひきずり

込んでくれます。

11麟プログラマ明書 R

事長÷】 室戸乞 日

アマチュアプログラマのときには、ほと

んど経験することがなく、職業プログラマ

になると毎日のようにこなさなくてはなら

ない仕事の1つが「他人が書いたソースを読

むjことです。実際、職業プログラマになっ

て、初めのうちは自分が書いたソースを読

むよりも他人が書いたソースを読む機会が

多pことに驚かされるはずです。

もちろん、アマチュアプログラマで色他

人が書いたプログラムを読む場合はありま

すが、その理由は、「まったく未知の分野の

プログラミングをする参考とするためJなど

がほとんどです。これが、職業プログラマ

になると、

-他人が作ったソースの保守/デバッグ/

パージョンアップをするため

・作った本人が途中でいなくなってしま

ったので引き継いだ

・大規模なプログラムの一部を担当し、

他人が作ったパートと接続するため

などの理由で、やむなく他人のソースを読

んだり変更する必要が出てくるのです。

本誌の読者ならオープンシースのプログ

ラムをいじったり、あるいはオンラインソ

フトのソースを入手して自分なりに改造し

たことがある方も少なくないでしょう。し

かし、そういったソースしか読んだことが

ない方にとっては、プロの現場で出回って

いる壮絶な汚さのソースや、それをめぐっ

て起きている泥沼と汚辱にまみれた世界は

理解しにくいでしょう。というのも、オー

プンソースやオンラインソフトは不特定多

数に見られると意識しているせいか、汚い

ソースというのは案外見かけません。「ええ

っ! あの程度で汚くないと評価できる

の? J と叫ぶ読者も pるかもしれませんが、

|特|鏡店|危険なコー科ング癖ら排除引

プロの現場では、

・納期があるので、美しいとか汚いとか

いっているヒマはなく、とにかくソー

スを書く

・仕横変更が平気で起こるので、途中ま

で書いたソースを中途半端な状態のま

ま書き換える

-元のソースを書いた人がいなくなって

しまったので、意味がわからないまま

書き換える

-自分が書いたものであっても書き換え

や安直な対応が多すぎて意昧不明にな

っている

などの原因で、かなり煩雑なソースが書か

れます。当然ながら、そのようなソースは

完成するまで時間がかかりますし、おまけ

にパグも多いので、ーから作り直したほう

が品質がよいものが早くできるのではない

かとひそかに思ってしまうほどです。

\聞と問の定義ところで、プログラムのソースを「美しい

/汚いJr上手/へた」と評価する場合、読者

のみなさんはどういう基準やどういう観点

で評価しているのでしょう。主観や個人的

な感覚だけで評価しているのではないでし

ょうか。

たとえば最近、筆者はüst1のようなプロ

グラムを見て、ダメなソースだと評価しま

した。ちなみに、このオリジナルソースは

381行ありますが、全部を載せるわけにはい

かないので一部を割愛しています。それで

もあまり気持ちのいいものではないという

雰囲気が伝わるのではないでしょうか。

しかし、もしかしたら中には「これのどこ

がだめなんですか? Jと反論する人がいるか

もしれません。また、「これよりもダメなソ

ースはいくらでもありますから、それらと

比較するとマトモなソースではないです

か? J と評価されることだってありうるでし

ょっ。

実は、「美しP/汚いJr上手/へたJ と p う

評価は、注意しないと客観的なものではな

く、主観的/感覚的な意味しか持たなくな

ります3 そうなってしまったら、単に他人ぱとう けんか

を罵倒する表現、喧嘩の原因にしかならな

くなります。

結論からいうと、筆者の評価基準は、

.あるソースを理解したり変更するのが

容易であるなら「よいソースj

・その反対に、理解できなかったり変更

が困難であったり不可能であるなら「ダ-メなソースJ

です。しかし、この基準も注意しないと、

-他人のソースを理解する能力は個人差

がある

・他人のソースを変更する能力は個人差

がある

-そもそも他人のソースを理解したり変

更する能力は上級者でないと無理

などの反論にあう可能性があります。

しかし、理解や変更が容易かどうかとい

う評価基準は多くの人に試してもらうこと

で統計をとることができ、そこから客観的

な評価基準として確立することが可能では

ないかと思います。実際に、あるソースを

複数のプログラマに改変してもらうことで、

そのソースの品質を客観的に判定できるよ~

う統計をとっている例もあるそうです固

l 制御じやすさ1 ::: l 書きやすさ/読みやすさ:\よいソースかダメなソースかを決めるの

に「美し P/汚pJや「上手/へたJではなく、

「理解しやすP/理解しにく pJや「変更しや

すい/変更しにく pJ という基準があると述

べましたが、これとは別に、

・制御しやすさ一一自分の土俵で勝負し

やすい/自分の知らない土俵や不利な状

況に陥りにくい

という基単もあります。

たとえば、ずっとCOBOLを使い続けて数

十年の経験があるベテランプログラマにC

言語でプログラムを組ませると、 2-3年程

度の経験しかない初心者が組むソースより

もデキが悪いものを作ってくることがあり

ます。

特集2 危険なコーデイング窃を排除せよ 43

Page 3: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

それは彼の「土俵J以外で無理に仕事をさ

せたせいです3 彼の土俵に収まっているC

OBOLでプログラムを組ませれば、デキが

悪いプログラムを書くとは思えません。反

対に、ずっとC言語や'Javaばかりやってきた

筆者のような人聞がCOBOLの仕事をさせら

れたなら、おそらく最悪のものができるで

しょう。なぜなら、そもそもCOBOLの言語

~仕様を知りませんし、 COBOLでよいプログ

ラムを作るノウハウをまったく知りません

から。

ません。そのあたりは当事者に確認しない

とわかりませんが、肝心の当事者が逃亡

(笑)したので確認しょうがないのです。に

もかかわらず、筆者が不慣れな土俵のVisu

al Basicで書かれている üst 1を「ダメ J と評

価したのはなぜなのかですが、その理由を

書く前に説明しておきたいことがあります。

それはソースを作成するのが楽なのか苦し

いのかということで、

しやすい

という基準があります。

実は、よいソースかダメなソースかの基

準は「読みやすさ」にあるのです。

最初に述べたとおり、職業プログラマに

なると、アマチュア時代のように自分がプ

ログラムを書き、自分が自分の書いたプロ

グラムをテストしたりデバッグする機会よ

りも、他人が書いたプログラムをテストし

たりデバッグする機会が目立つて多くなり

ます。そして、「制御しやすさJ と「書きやす

さJの重要性は、他人が書いたプログラムに

おいてはかなり低くなります3

・書きやすさ一一コーディングが楽であ

る。めんどうくさい入力を回避しやす

実をいうと、先ほと:üst 1に示したVisual

ßasicのソースをダメと論じたことについて

も、単に筆者がVìsualBasic に不慣れだから

ダメだと評価したのかもしれないのです。

ひょっとしたらVisual Basicを書く人たちに

とっては、このスタイルが楽なのかもしれ

という基準があります。これが話をややこ

しくする元凶なのです。よく覚えておいて

ください。この基準とは別に、

.読みやすさ一一書かれている内容が理

解しやすい。書かれている内容を改変

アマチュアプログラマとして自分がプロ

グラムのすべてを書く場合は、自分が制御

しやすいように、そして自分が書きやすい

ようにソースを書けばいいのです。もちろ

44

ダメなソースの例 (Visual 8asic .NET)

'毒事事申事事申 事事事事温血事寧申寧事寧事事事事事寧''-fj査状垂ログアータ鶴屋両' ー-①'事n....串柿帥事事帥"“"“..'pub 1 ic s¥lb Bxp副ιE回saL<ロgOata( ByVa1 xPtype A� Sbort , ByVal block..Data() A8 BYteJ

Dia thePalle _ A� Lonq Dia al'ront..Jt舗r A�-Lono D~II ~yalu・ As 10珂Dim index 18 Short

'....ブロックデー夕刻D!X取得....む泊・X" 0

Select Case xPtv国ca� KBNSALC氾JtAHIT

'事申事機査技況ロヲ'ベーン1霞の取南凶1・Page = (為 I cJ

)(UWCI吹lRIJ>AIWLLBHG'rIu引lRIJ>~開明(K副

'フロントノリ!アaal'ront..Jt鈍r =α

,11伺JtAl(I'l'ORLPAlWLLE\lρGJWIlTORLPARA比よ.BN

'インテ";1 クズ取得附1ile True

If_gJぬfore(lta(ù姐皿)-,befpr~all叫u.. aPront..Jt岨r) '1'h回

Bxit 峨úle

EiJ=Ud・x + 1 ② If ・JIe~or・Col Data. Len~ <む叫眠持制

吉凶iIIPr・øerve 江.Befor・ColData(ind回}R阜01a g 踊fore(元1b此a(in�x) . scanning_data ( 1 ) EXltw6IT�

勘ld If 監Id 附úle

'ページ敏の取得g-B・for・Col Data(ind且X) .beforePageHUII "白IcJ)eciu 1 Int (C& 1 cJl 白IbI oclLData , K副SALQGJWII'I'ORIJ>ARA札r.s:阪湾民 E聞SALOGJWlITm回), 1 , IC悶SALOG_IWIIToRLPARA比.LmねTHIX聞SAL∞_KAHlTORLPr

'フロント/リア状兄の取得g♂・?Efolhta{iM札front..r制包 Calcムc皿lInt(CalcJl l∞

:k~ta , K副SAIρGJWllTORIJ>AlWLLENGT!:I. 国SAI心GJWlI'l'ORII~~T , 1.'X副SAIρGスAHlTORI_PAlWLLEHO'J潤[宜副SALOG.JWlITORI_F

喝ユプ 1) フラグの取得_BeforeCo lData (index) ._duDri " CelcJleciJIAllnt(Calc_BlockDat

L偽必強制班長虫財閥弘殿おも常識if沼'1{コードの取得!-hfOE蜘1 Data (ind回}却iC岨e = Ca l叩ill& lInt(白 lc_Bl∞k

oc[LDa!A,. X剖S11国ぷAHITORLPAlWLL刷。叩, KBHSAI正氾_KA!I1 TORL , 1 , KENSALCXLKA!lIToRLPAlWLL間町H岡田NSÅLOGぷAHI'l'OR LPAPBIL

'スライド情舗の取得間forぬlData(in似l.山e.刷th" 沼崎C瑚1 Int(calcJllo

91oçk..Data , K町s.w沼JWU'l'ORIJ>ARA札~.ー→ R副SALOGJW(ITOR:X J , 1 , K国SALOG_KA!lI'l'ORI_PARAM..LENC叩(1\:副ISAL∞.JAHI'l'ORLXDPL

g~~efo~eCoI Data(index) .slide.Height .. Calc.J)eciIll&IInt(CalcJlI 回at4Byteãl~Tõck:Õãtã 官瑚鉱工加点AMI何回Ï:PÂR訓ム副官官 官副SALOG_KA!lITO

RÏJi)PLhiõ田:T; -ì~KËÑsÁL伺,刷ITORLP服組三園町田1溜sWXiJ姐lTORLYDPI

C MAGAZlNE 2005 7

List 1

_1即鼠))'台サイズの取得gJl号foreCol DataUndex) . da.Lsize.Width "白lcJ冶ciullnt(calc_

B 1 ocltDat4Bytes (b I oclLData 賀町SALO<U<AHlTORLPARA札1.$回開嘗司SAL伺ぷAHI?銑邸副即IÈi!~î;間同ー刷fTãRT.:P胤札制鴎比一lTORLW

g-B・(~rec~I Data(index) .daL..ize.Height " C& lcJ)eci回IIntlC& lcJl l ocltData町tes!b I oc!t..Data , K割以LOOJWIlTORlJARA札LJ)ね叩 K副SALOOJW(ITORLHIGHT:'lND:回), 1 , K副SALOOJ細工明RIJ>ARAlU却価!l1t副SAL∞_KAIUTORLHIGHT_INDBXJ)

Case IæNSAL∞UtlIR剛 JNDBX可申申繍歪ロクMft.::R事事寧....ここはからつぺまだよ。ついでにオイラのおつむ色からっぽ{笑}柿事

i歩官~hl~BX d ジ霊Eの 1121得紅白 l両国l幻1dex).afterpageNUII " C& 1 cJ淘ci且IInt(CalcltJ)事ta. K副SALOG_α1rPAP回GBT..PA且凶 1酬明暗 E副SALOCL揺i7-i,間以LOCL畑PAPmGÊ口組~(x副SALOCL

弊ジリア状況の取得erCol Dataはndex) : front..reaa " Ca 1 cJ)・cill&lInt(CalcJ) Data. K副ぬLOG_αl'l'PAP四.GBTJ>A1WLLBNGTH, I覩SALOO_OU md , 11msm-mu郎町刷U郎副KBHSALOプリフラクの思禍ercoll泊凶{幻泊êx) ._dupri " Calc...DecillWl1 Int(C&lcJllock d 闇SALOG.訓lTORIJ>胤~~も34mm訓lTORLAIρG_KAHIT叩LP飢A札LE献miI KKNSALOO_貼HI'l'OR1JlIJPLL

., (8rrぷん省首)..

C&se X副s.u.oG_OO'l'皿ALINDBX'寧申申出力耳解m: t.. '寧事事玄之政がちづ 11'だよ。ついでにこれを臨んでるお前のおつむもから

っぽ{笑)帥申

cm,S?sdi時出抑制 ③ .. (15符:5: ん省略1. ..

caøe, m~ 動'?J}7iI.~~カ対策榊(U仔ぷん省略)...

CMe, R?喫斡ぢ塑?ぬ鄭男析州thepage .. CalcJl町illAIInt (Ca 1 cJl l∞kDataByteø(b(ockJ)ata , KEN

SALOG_SCANDAT",-PλRA民.L関白H , XBNSAI正焔_SCANDATLProID:'t~BX). 1. K闘SALOO_SC,馴DATLPA1WLLÐ拘叩I J(園SALOG_~ATA..ProID_INDBXI )

al'ront..Jtear " Ca lcJ淘ci皿lIntC白 1 cJl lockDataByt回 (bloc~ta ,K副SAωG_SCAHDATA..PAIWl.LENGTII. x副SALOG_民AHDATA...FrontJ{蝿a..lND草草, 1 , t 副SAI札詑脱出TÃ:YA両む誌研加SALOG_SCAND1'札..Pront陶aa..um回}…-. . ..

'インデックス取得地i1 e True

If g_BeforeCo i[油ta( !ndex ).beforePageNUII" O.or (!LB・foreCoI Data( j.ndex l. befpr~Pag甜UII .. thePage And gJlefor・Col Datalお由xT.lrolitieaa .. al'ront..Jtear) 'ft¥en

Bxit 地ile勘ld If index " index + 1 If g..BeforeColData.Lenath < index Then

lg担問~:C~i誌:部↓~~説~rn~_data(1)

開-­

T

Page 4: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

,得意なプログラミング言語で、自ます。 たい流儀で好きなようにやれるの

スかダメなソー.ころが職業プログラマなどになっ

にあるのです。‘数でプログラムを組むチーム開発おり、職業プロ 3からソースを引き継いだ場合、他ア時代のように!なように書いていたソースは自分当分が自分の書「はみごとに読みにくいのです。そ七りデバッグすt況では「読みやすさ Jが強く求めらモプログラムをう

現会が目立つて去で、他人にも通用する読みやすさ御しやすさ」と rt" ょうとすると、自分ひとりが制御人が書いたプロ~)ように、書きやすいように気まま:なります。こられた状況とは状況がまったくfラマとして白土きます。読みやすくするための工害く場合は、自士、たとえば、きちんと統制のとれ?して自分が書全ィング規約に従う必要が出てきま「ばいいのですぞんなに書きやすかったとしても論

島村

Brlt ぬileB泌 lt

制崎県虫色,、ーン番喝の取得

i特|集121危険なコー内ング癖ら排除引

理を追いにくくするような長文や複雑な入

れ子、グローパル変数を避けなくてはなり

ませんコそれまで気ままにプログラミング

をしていた人は、この時点でとたんに制御

しにくいと感じたり、書きにくいと感じる

のです。

理想論だといわれてしまうかもしれませ

んが、

-制御しやすさ

.書きやすさ

・続みやすさ

が同時に向上する工夫があるなら、それを

採用すべきなのです。あるいは読みやすさ

を向上する工夫をすることに苦痛を感じず、

むしろ、その工夫を守ることが制御しやす

さと書きやすさを得る方法だと感じる境地

に至ればいいわけです。どちらかというと

ust 1

後者を勧めるのが筆者の意見です。

iダメな込ι丈民平価した理白山富'~a;副~

さて、ここでUst 1のVisua1 Basicのソー

スをダメと評価した理由を書きましょう。

問題点はいくつも見つかりますが、 1I闘に見

ていきます。

砂 1 つのメソッド(関数/手続き)が長文

Ust 1はコメントや空行を含めると381行

もありますが、よく見るとこれで1つのメソ

ッドになっています。 1つのメソッドの行数

が長いだけで、このソースを追いかけよう

という意欲が薄れてしまいま-す。

ところが、現実の開発現場では381行な

んでいうのは、まだかわいいほうで、 1つの

tr(c通 lにD・c1凪lInt(C冶 1 cJll ockOataB ・.(bl 以:k..J)ata. DIISAI却ー民AHDAT,色 B

議T還思忍邸磁芯俊治部7zMt4S盟主日恕訟法悦i;raft・rSbot.. 0ιR胞a

l出" Ca lιDeci..llr

I函碗沼田地寂誌

F批山崎c出」諏蔚T福地局コ百

g_Be~ぽ氏。 !llatal~時岨). Qefor.p,珂叫ua >5 tb.師"~f即時且l PJ.t 担金絡l. fr∞tJ"・岨.. arr∞tJtear .^~ヤシ岡田官軍U}思葡

Brlt Whi l ・End If aGrIlllhi四 IInd倒.. aGra凪i伺IInd回+ 1

01. theS個且Po8Þ ,ぬort

t!leSCanP08 .. g_Be~or・Co lI泊ta ,l!el!g均theScanPo8 '" g....gefo~・CõiõãtãlIñdåxì. scann1n!1_data.Leng出

'..・画像香号を取ります...廿leScanP08 • 0

,の8つむもからっぽ(j

附l11e True 且 If gー陶foreCoIDIlta( index) . 8cann1ng_data (theScanPo日) .after Plot .. 0 Then

=白 IC_Dec1皿IInt吋Brlt 崎11le制札期制 K闘訓 刷 If湘札L回制(x問調 theScanPol "凶凶C明P08 + 1

1a If gAfoMolMt叫ndaxI . Bcann1珂_data.凶gth < index Th

悩鵡組出t刷凶canPos) …旭町一ー山伽ぬI Data(in加}…i札叫(t,'_PAR醐ム曲活i7K富; ~~ill_ .9>.foreColOatalin品川.8canning_data ( 1 )

一一一同山田'''l Bxit Wlille End If

P.eciallnt(C山..Blj ~刷1.~滋?d担調位邑凶iぬ1153gor山l同国{印刷 .S国ooi札伽ta(凶eScanP叫afterぬot ..

…!1_Grapbica 1 Index = g_Graphica 1 Index + 1

'走査開鎗位置の取得g,.Befo reCo 1 Dat{l. (10(

l些伊!l,J[ .. cåIë.J5eëi踊 IInt日正Ic鴎んでるお前のおつむF岳部件回岡崎,国S叫九8~

μ四回!lK悶SALαL詑AHDATA..ιM4 自..BaforeCo1 DIlta (in� at1∞,~ = cãIë.:.Dèëi皿iíñtI五iëI~ÅTAJ'総ÄK:L副G'l'H. J(副団UImam-山ATA..Pで揃E

走査舗の取得gJ!eforeCo I モata (in�

ie・山← cãlë':ñe叫川?C~!~.l~扇面白川SAI札,以靴店踊TK副w..c泊_8叫NDA札!白geforeCo1Data( in且

器部総\~~単純錦織;援協議EEtisziど出,~塗りつぶしキーの聞氏自叩.a..lliDBXJJ i =祭主CCaJc涜f:m~rciT~~討

1&縦T困問問邸調L8側m!L副IGTHTi<副sÃL60:Sè凶OATA..KOP_I

~g凶U皿 = Q or (aJj 説。 iÏiata'i~~ ~!!.塗りつぶしキ-(1,

D1II aGraphicallndex

除草 Incanning_data( 1)

aG写aphicallndex " 0 附l11è Tru・

If !1_Gr匂恥1caI Data(aGraphica lIndexl.key_of..pa!nt = ..明1・

IÞdt 附111.ElseIf g_Graphica IData(aGraphica 1 Index) .Icey_oLpaint = CS

If 笥持ffJ山.Length <前哨i四IIn郎M

End If End禍l11e

h報!5持抑1dl Fiat却旬以.~.)

7塗りつぶし前兆ログを

(index) .!fïë%~~叩品凶血d l L防相11ゐ IDa凶店直x) • fro�J'eaa ex) .8c.Æ日守?JEぷ2531品1

End 1f

…:号機搭齢的'Case,母:52帯Z4践はアウげツトじω宇刊なt.t~,州Lい川、刈幌(幌即笑

一caae, l男守灘鴻tずFJふ~?いする。今日も早退問山事白帥.勾指事法晋r~~EX

c… ~'ft照明面1~平CUelt将棋£瀦r~~1NDBX

CaslI吋~Nf2d宮嘗P~:-~ND回

ωー?押指紋鶴何回CUeK月4鵡就寝節子1ND邸

ca811.~ft椴湾首r~~:-I即回

句se.~曾照d量街_1戸田町ld Select

Knd Sub

特集2 危険なコーディング徳を排除せよ 45

-

Page 5: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

メソッドの行数が3,∞0行とか4,制加行と p う

正気を疑う長文メソッドもさほど珍しくあ

りません。ですから、これを指摘しでも「な

んだい、たったの381行程度かjとパカにし

たように反論されることもあります。しか

し、よりひどいものを基準にして、それよ

りもマシだからといって情状酌量する理由

にはなりません。殺人より也マシだから万

a‘引きぐらい見逃せといっているに等しいの

です。

1つのメソッドがどれぐらいの行数になる

のがよいかは、その開発現場や個人によっ

て基準がバラバラです。パソコンの1画面に

入る行数、つまり 25-鉛行程度という人も

いれば、 100行程度までならOKという人も

います。

しかし、さすがに381行くらいはOKとい

う人はあまりいません。いずれにせよ長い

行教のメソッドはそれ自体が頭脳に対する

暴力であり「読みやすさ」という点では十分

に失格です。

砂Caseラベルが多い

これは長文になる理由の1つでもあります

.が、 list 1はSelect-Ca健文 (C言語のswi帥・

沿se文に相当)の中身が長いですし、 Case

ラベルも多すぎます。数えあげると 18個も

あります。そのくせ、 Case Else(C言語の

de白ult'こ相当)がないというのはバランスに

欠けています。詳細は、「状態遷移対応J と

してのちほど説明しますが、 Caseラベルが

多いのなら、そもそも違う設計やコーディ

ング手法を検討すべきなのです。

長々と書くのは「書きやすさ J という点で

はメリットがあるのかもしれませんが、「読

みやすさ Jを無視しています。また、このよ

うに長々と書かれたソースではプログラム

にパグがあった場合、追跡がめんどうにな

ります。もちろん、将来の仕様変更や仕様

追加もめんどうです。

安直にCaseラベルを増やしていく人は、

おそらく「追加? そんなのCaseを 1つ追加

すればいいじゃんj とでも思っているのでし

ょうけども……。

46 C MAGAZINE 2∞5 7

砂変数名に統一性がない/

変数名か5機能の憲昧を読み取れない

複数のプログラマがグチャグチャと書き

加えた場合にありがちなのですが、 list 1で

は変数名の傾向にまるで統一性がありませ

ん。というのも複数の単語をつなぐ方法が

fthepageJのようなキャメルスタイル[制が

あるかと思えば、 fa_valueJのように「一(ア

ンダーパー)Jでつなぐスタイルもあったり、

f aFront_Rear Jのような両方を採用している

ものがあり、わけがわかりません。さすが

に rxJf pIJf zQuwJのたぐいの短い字数や暗号めいた命名をしていないのはマシですが、

かといって rthePageJ とは何のページなの

か、 raFronCRear J とは前後を意味するので

しょうけど、何の前後なのだかは意味不明

です。

[注1]単踏の縫合箇所を小文字と大文字の変化で喪現するスタイル。ラクダのコブを連想させるからキャメルスタイルと呼ばれ

砂コメントが安直/ムダな装飾/

ふざけたコメント

list 1①はメソッドの機能を説明するコ

メントです。しかし、まったくないよりマ

シでしょうけども、それにしても説明不足

です。そのくせr'*-*' Jのようなコメン

トの文字数に合わせたよけいな飾りがムダ

です。コメント内の文章が長くなったら

r*Jを増やすつもりでしょうか。飾りなど

いりませんから、引数、すなわちxPtypeや

block_Dataが何をするものなのかの説明を

入れてほしいものです。

そして腹が立つのはlist 1③のふざけた

コメントです。さらに、List 1-G)は「喧嘩を

売っているのか? J といいたくなりますし、

ほかにもなれなれしい口調や真剣にやって

くれといいたくなるようなコメントが多々

あります。

全般的に必要なコメントが少なく、たま

にあっても内容が薄くて役に立たないのに、

本人はユーモアのつもりで得意になってい

るのかもしれません。コメントによけいな

情報や説明不足、おふざけは禁物です。こ

ういうコメントを書くことで本人の人格が

疑われでもしょうがないでしょう。

砂グローJ(Jレ変数を使いすぎ

listlで「ιJで始まっているのはグローパ

ル変数です。とくにアマチュアやしろうと

に多いのですが刷、きちんと設計をせずに

いきなりキーボードからカチャカチャとソ

ースを打ち込むという安直なスタイルに慣

れたせいで、なんでもかんでもグローパル

変数にする習慣がついたプログラマがけっ

こういます。

実をいうと、「書きやすさ」という点では

グローパル変数は最強で、他人のプログラ

ムを修正するときでも、「きちんと解析する

のがめんどうだJ とぱかりグローパル変数で

パッチを当てまくったり、バイパスを作る

という手法がとられやすいのです。そして、

さらに困ったことに、こういう人は「仕事が

速いJと間違って評価されることがあります

が、仕事が速いのではなく、単に「手抜き

工事Jがうまいだけにすぎません[制。結局、

処理の意味をロクに理解しないまま手を加

えているので、あとから重大な問題が発覚

したり、手抜き工事の影響で、ますますわ

けのわからないソースに変貌してしまい、

そのうちいき詰まって破綻することになる

のです。

[注2]悲しいことに職業プログラマにも多

[注3] 正直に告白すると、かつての筆者がそうでした。今はもうしていません。さんざん痛い自にあっで、こりたからです

惨 1 行に詰め込む文字数が多い

たとえば、List 1②では、 E文の条件式

があきれるほど長くなっています。

横方向にやたらに文字が並んでいるのは、

前述した縦方向にたくさんの行数が並んで

pるのと同様、「読みやすさ jという点で失

格です。ところが、困ったことに横方向に

文字がたくさん詰まっていることを「カッコ

L 叫リとか「こんな複雑なプログラムを書け

るオレ様ってすてき」と書いた本人が勝手に

勘違いして、陶酔していることがありがち

--

Page 6: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

!磐.\.12J危険なコー科ング癖排除引

評価する人など誰もいないでしょう。もし

いたら、よっぽど鈍感なのか、これよりも

ひどいソースにふだんから慣らされていて

感覚がマヒしているのでしょう。

E‘ 、・

τ二三三 .づ二』三 三".

かけといつも思うのです。

Iistlには、ほかにもここはまずいという

部分があるのですが、いずれにせよ、この

ソースを手本とできるようなよいソースと

です3

筆者はこのようなソースを目にするたび

に「自らの低能を証明しているようなものな

のに、この人は恥ずかしくないんだろう

、,‘,

.•

、,

と、どこにぺーストしたのかを探しまくる

ハメになったり、あるいは探しきれずにト

ラプルに巻き込まれるという情けない事態

に陥ります。

対策としては、コピー&ペーストで済まa・

すのではなく、共通しそうな部分を 1つι

メソッドにまとめてしまい、それを該当箇

所から呼び出すスタイルに変更するのが正

当な手段だと広く認識されています。

ところが、コピー&ぺーストが多い人を

観察すると、「コピー&ぺーストが習慣に

なってしまい、もはやそれなしではどうす

ることもできな L リといった、まるで禁煙

ができない愛煙家のようないいわけを聞く

ことがあります。こういう人たちにとって

は、共通部分をまとめる作業自体が苦痛で

あり、うかつにまとめる作業をやってしく

じるよりも、安直にコピー&ぺース卜する

ほうがマシという間違った考えに染まって

いる傾向があります。

彼らを弁護するわけではありませんが、

さまざまな事情から一時的な避難措置、た

とえば緊急に対応しないとまずい状況なの

で、目をつぶってコピー&ぺーストするこ

言語のいずれにおいても観測され、深刻な

被害をもたらしています。これらの三大疾

病をなくすだけでも、ずいぶんソースは読

みやすくなり、パグ追跡や仕様変更がしや

すくなります。

砂コピー&ペースト愛好症

エディタでソースを記述しているときに、

ある箇所をコピーし、そのまま別の箇所に

ペーストするという行為を連発する症状で

す。困ったことに、この行為は仕事を早く

終わらせるコツだと間違ってとらえられる

ことが多く、コピー&ぺーストが上手な人

がすなわち、プログラムの上手な人と勘違

いされていることも多い、やっかいな実情

があります汗

ところがコピー元にノ fグがあったり、仕

様変更や追加でコピー元で改変が生じる

L娘師みる原因とな語、l三大尾5F J 糧 ‘即日本人の死亡原因として割合が高い三大

疾病とは、

-ガン

・急性心筋梗塞

.脳卒中

ですが、ダメなソースを生む三大疾病も同

様にあり、ソースの破綻原因として割合が

高いと思われます。すなわち、

・コピー&ぺースト愛好症

.長文愛好症

・グローパル変数依存症

があげられます (Table 1)。もちろん、この三大疾病は筆者の個人的な観測で、すが、現在よく使われている手続き型プログラミング言語やオプジェクト指向プログラミング

nHHbzu

U

、時‘ト

v、

47 特集2 危険なコーディング磁を排除せよ

症状

コーディングをするときにすでに、入力したコードをコピーして持ち込むのが好き

1つのメソッド(関数、手続き)がやたらに長文に怒ってしまう

ローカル変数や引数にするよりも、クロ-/()レ変数に頼ってしまいがち

Table 1

2

LTJAV国

b「J

Page 7: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

ともありえるでしょう。ただし、応急治療

をしたあとで、必ず本格的な治療を施すべ

きかと思います。たいていの現場は応急治

療で問題が解決したと勘違いしているフシ

がありますが、それは絶対にやってはなら

ないことです。

あるいはデバッグの手段としてデバッグ

出力のコードをコピー&ぺーストすること

~まありがちですが、このときも、その前後

.ニデバッグ出力であることを目立たせるた

めの目印を埋め込むべきです。たとえば筆

者は、

普ifndef NDEBUG

普回dif

をデバッグ出力の前後にはさみ、本番では

NDEBUGを定義してコンパイルする工夫を

します。

コピー&ぺーストとは別の問題ですが、

どこまでが本来のコードで、どこまでがデ

バッグ出力なのかを区別しないまま記述を

混在させることもプログラムの読みやすさ

を損ねるので絶対にやめるべきです。

共通部分を 1つのメソッドにまとめる以

Fig.1 単純な分岐

void f() {

if(判定1() )( /事判定1が真のときの処理 M

}else(

/事判定1が偽のときの処理申/

Fig.2 判定条件が2つの分岐

void fl) {

if(判定2() )( if(判定1() }{

付朝l定2が真で、判定11.J"真のときの処理 *1)else{

/事判定2が.で、判定lが偽のときの処理事/

)else{

if(判定1() )(

/傘判定2が倫で、判定1が棄のときの処理常/)else{

1* 判定2が偽で、判定1が偽のときの処理事/

48 C MAGAZl悶捌5 7

外にも、ループにまとめてしまう手法凶]も

あります。また、実行効率を気にするなら、

C++のインライン関数を使うという手もあ

ります[出

[注4] 今月号の本誌連総記事「プログラミング雑技厳J (121-124ページ)のUst2を参照[注5] キャッシュの問題があったり、通過回数が少ない場合は本当に有効なのかははなはだ疑問ではある

砂長文愛好症

文字どおり、 1つのメソッドの行数が長ふかん

くなり、全体を簡単に傭敵できなくなる症

状です。長文のメソッドが作られてしまう

ことを困ったこととは思わず、「こんなに

長い行数のプログラムを書ける自分は賢い

のだj と勘違いして自己陶酔する患者もよ

く見られます。実際には、長文が多くなる

のは賢いのではなく、単に要領が悪いだけ

の話なのですがコ

1つのメソッドが長文化する理由はいろ

いろあります。代表的な理由として、コピ

ー&ぺースト愛好症との併発とか、共通部

分をサブルーチンに切り取る能力の欠知な

どがあげられますが、職業プログラマの場

Fig.3 判定条件が3つの分岐

void f()

if(判定3() )( if(判定2 (J )(

if(判定111)(

合は、仕様変更や仕様追加の頻発による安

直な対応も長文を作る原因となります。

たとえば最初はFig.1のような単純なif文

であったとしましょう。ここで仕様追加が

あり、判定1とは別の判定を盛り込むよう

な要求が出たとしましょう。安直なプログ

ラマは、 Fig.2のようにコピー&ぺーストを

し、差が出てくる部分をちょこちょこっと

書き換える措置で逃げます。こんな調子で、

また仕様追加があって別の判定を盛り込も

うとすれば、Fig.3のようにして逃げます。

さらに別の判定を盛り込もうとすると…-。

これ以上は、あまりにもパカパカしいので

書きませんが、 Fig.3の中で「ーのときの処

理J とコメントされている部分が16個、 32

個、……と倍々に増えていきます3 この調

子でどんどん判定を盛り込むとメソッドの

サイズが指数関数的に増えていき、いずれ

プログラマの頭脳が追従しきれなくなって、

破綻することはいうまでもないでしょう。

砂グローバル変数依存症

グローパル変数は、プログラムのあらゆ

るところから参照や書き込みができる変数

/事判定3が真で、判定2が真で、判定1が真のときの処理事/)else(

1* 判定3が翼で、判定211真で、判定1が偽のときの処理 M

}else{

)else{

if( 判定1IJ)(/害事l定3が)1で、判定2t1偽で、判定1が真のときの処理 M

)else{

州事l定3が真で、判定2が偽で、判定111偽のときの処理事/

if(判定2() )( if( 判定10)(

/事判定3が偽で、判定2が)1で、判定1が真のときの処理 *1)else{

1* 判定3が偽で、判定2が.で、判定1が偽のときの処理事/

}else{

if(判定1() )( 付朝IJlt3が偽で、判定2か偽で、判定1が真のときの処理事/

}else{

1* 判定3が古島で、判定2が偽で、判定1が偽のときの処理 *1

Page 8: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

!蝉lZJ危険なコー予{ンク癖排除世

対応としてグローパル変数が濫用されてい

ました。エラー表示をするための文字列変

数(グローパル変数)を書き換えると、別の

ところで計算している合計値がおかしくな

るパグがありました。原因を調べると問題

の文字列変数が、なんと計算ルーチンの入

出力パラメータとしても利用されていると

いう信じられない作りになっていたのです。

前述した③の現象にみごとにハマったのでー

す。

しかたなく、エラー表示をする前に、そ

の変数をどこかに退避し(当然、これもグ

ローパル変数)、表示が終わったら退避し

た値を変数に復帰するというパッチを当て

たのですが、あとからよく考えると、そも

そも入出力のパラメータとしてグローパル

変数を使うことや、エラー表示の引数にグ

ローパル変数を使うこと自体がおかしいの

です。

おそらく、このプログラムを最初に手が

けた人が古い仕様のCOBOLやアセンブリ

言語に慣れていてローカル変数を知らなか

ったのではないかと想像します。

[注6]対策としてrvolatile修飾子Jを付記する

...., i L要 三二三(4U:主つiî,, 'c;

J幼稚砿コ示ラケこぎグ46長t;~~r:,.'::'; ~ 1 間違うた構造化竺ゲヤ記長; ~.~ I:,,~,:手7三与二~..;二で当与...・ ~"~:'dl ':,':.'盛山ユ2どー・二三

三大疾病は重大な病気ですが、ごれより

も軽症なものでも見逃すわけにはいきませ

ん。軽い頭痛だと放っておいたら、実は重

大な病気の前兆症状であったということが

ありがちです。

ここでは、重大な症状や三大疾病の原因

にもなりうる症状を示しましょう。 Table2

を見ただけでも、どういう症状かは想像で

きると思いますが、「捜雑な入れ子愛好症」

と「途中リターン依存症JはのちほどPart 3

の f30PEN-3CLOSE対応jの解説部分でも

説明します。

また、「ケースラベル多発症」也同じく

Part 3の「状態遷移対応Jの解説部分で取り

上げます。ここでは、「横方向肥大症Jf記

号定数多発症」を説明します。

⑤グローパル変数を利用したモジュール

をライブラリとして再編成することは

困難で、ソースの再利用がしにく \"'0

そのため、すでに書かれているコード

があるにもかかわらず車輪の再発明を

繰り返したり、すでに書かれたコード

からコピー&ぺーストで取り出したも

のを改変して再利用するはめになる

などの問題点が考えられるからですリ

いずれにせよ、どうしてもグローパル変

数でないと困るケース以外は、極力グロー

パル変数の使用を避けようというのが「常

識Jで、避けるための工夫が昔から考案さ

れたり提案されているのですが、案外、知

られていなかったり、興味がないからなの

か無視されているのが実情のようです。

また、オプジェクト指向プログラミング

をきちんと理解していたなら、そもそもグ

ローパル変教を作ることすらなかったり、

仮に作ってもプログラム全体からは参照の

みしかできない安全なものを作ることがで

きるのですが、どうもListlのソースを作っ

た人をはじめとして、まったく理解してい

ない人が多いようです。

以前に筆者が頭を抱えたグローパル変数

がらみの障害を紹介しましょう D あるOA機

器のプログラムで、 4人がかりで汚く書き

換えられたソースだったのですが、安直な

です。

しかし、「あらゆるところからアクセス

できる j という特性は、注意して使わない

と危険な落とし穴を作る原因になります。

というのは、

①グローパル変数はプログラムのあらゆ

るところで参照したり書き換えること

ができるため、どの場所での参照や書

き換えが原因でパグが起きたかを追跡

するのが困難になる

②グローパル変数の組み合わせによる分

岐条件が多数できてしまう可能性が高

く、この場合、あとからすべての条件

を検証しにくい。いわゆる「組み合わ

せの爆発j現象で悩むことになる

③同じ名前のグローパル変数が別々のと

ころで宣言されていたり、 1つのグロ

ーパJレ変数を複数箇所で違う意味に利

用していることによる「予期しない衝

突jが起こりやすい。また、その修復

をしようとしても「あちらを立てれば、

こちらが立たずj現象で悩むことになる

④マルチスレッド環境で1 つのグローパ

ル変数を2つ以上のスレッドが参照し

たり書き換えることによって起きる弊

害がありうる。またコンパイラの最適

化によって参照や書き換えができない

ケースがある[注61

る安

証文

旧が

k う

3 グ

トを

〉と

で、 i

S も

す。

。河凋詑哨の怖いずし

49 特集2 危険なコーディング書揮を排除せよ

Table 2 重大な症状に発展する可能性のある前兆症状

名称 症状

理雑な入れ子愛好症 if文やwhile文のネストが深くなってしまいがち、構造体の入れ子が深い

途中リターン依存症 途中return文をよく使う、その結果、改変のときに解放などの後始末を忘れる事故を誘発する

ケースラベル多発症 caseラベjレがやたら多く出現し、その結果switch文肥大症も併発する

績方向肥大症 1行に詰め込む文字数がやたら多くて、縦方向の長文と同様、意味がつかみにくい

短文愛好症 長文とは反対に短文のメソッドが大量に出現し、かえって意昧がつかみにくい

匝枕ontin峨存症 breakやcontinueをよく使うため、プログラムの流れが追いにくく怠る

記号定数多発症 #define芯どを使って記号定数を記述しているが、必要以上にやりすぎている

例外処理錯誤症 例外処理を「例外的芯事態jととらえるのでなく、通常処理の一環として利用する

Page 9: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

F

含めて、わざわざ別ファイルにまとめてい

る例も見かけますが、こうされると目的の

値を調べるのが手間になったり、記述を変

更するのはどこなのかと探し回るはめにな

りますコ

l 議崎元主主1ぬ:験反点滅;1E間違つ左右プジ主2ト指向当主協議越訴援き~:~~~起訴i~~民忌ミむ

最近はオブジェクト指向プログラミング

言語が普及していて、すっかりオブジェク

ト指向が定着したかのように錯覚する人も

pますが、実態はかなりお寒い状況です。

ひょっとすると、筆者がたまたま目にする

ソースにひどいものが多いだけなのかもし

れませんが、たとえば、 C++でクラスと称

しているものの中身が構造体とほとんど変

わらなかったり、Vìsual Basicでクラスと祢

しているのがモジュール(Mod凶e) と変わら

ないしろものだったりします。

ちなみに、冒頭のUstlで示したVisualBa

sic .NETのソース也、あるクラスのメソッ

ドなのですが、このソースを書いた人はオ

プジェクト指向をまったく理解していない

と思います。少しでも理解しているなら、

とても書けないようなダメな記述がいろい

ろと見つかるからです。もちろん、こんな

ものはクラスではなく、モジュールに毛が

生えたようなしろものです。

ところで、「オブジェクト指向を理解し

ていなくても読みやすくて仕様変更や追加

際のコーディングではfPIJ を書きなさい、

ということです。そうすれば円周率の桁数

を増やしたいときに#defineの部分を書き換

えるだけで済みます。

ソースのいたるところに、直接3.14159を

コーディングすると、その部分を探しまく

って修正しないといけなくなり、しかも修

正モレをする危険性があります。

この教え自体は間違いではないのですが、

やりすぎた適用をされると、ときにはうっ

とうしいものになります。たとえば、 üst4

のような例では、 MAJCINDEXは妥当だと

思えますが(よく考えると安直な命名です

が)、 ZEROなんて用意する意味があるので

しょうか。さらにTENは10という値そのも

のを名前にしていますが、将来的に10以外

の値に変更されたとき、 τ'ENと書かれてい

るのに実際は10以外の数値だった場合、ソ

ースを読む人を混乱させるのではないでし

ょうか。またほかの場所で10の値であるも

のをTENと記述していた場合、変更によっ

て、そちらに悪影響を与えてしまいます。

定数の定義がたくさんあり、しかも暗号

めいた命名をされるとうっとうしいもので

す。なるべく意味が直結した命名を心がけ

てほしいものです。

また、たった1回しか使わない定数なら、

定義が必要かどうか疑問ですし、必要なケ

ースでもそれを使う関数の直前に記述すれ

ば十分でしょう。 1回しか使わない定数も

砂憤方向肥大症

長文愛好症(縦方向の長文)がよくないな

ら横方向ならいいだろうと短絡したかのよ

うな症状です。行数が少なL 刈まうがソース

を読みやすいからと、複数の行をむりやり

1行に詰め込んだ記述です。

たとえば、 llst2のような記述があったと

します。文字列を操作するコードが5行あ

Jのですが、これを無理に1行にまとめる

~Ust 3のようになりますc

Ust3はプログラムの挙動としては間違い

ではありませんが、「読みやすさ j という点

ではどうでしょうか?

筆者はこのテクニック(?)を某所で見た

のですが、考案した本人はずいぶん自慢げ

でした。 strcpyやs甘catの仕様をうまく活用

したテクニックのつもりなのでしょうけど、

何か勘違いしているとしかいいようがあり

ません。第三者が、 Ust3のコードを改変し

ようと思っても、すぐに意味が読み取れな

いでしょうし、へたにいじって支障が出て

こないか不安を感じるでしょう。

惨自己号定数多発症

「プログラムには直値を埋めるなj とよく4・・‘ i導されます。たとえば、円周率を利用す

る計算がたくさんあるのなら f3.14159J を直

接コーディングするのではなく、

普define PI 3.14159

という記述をソースの官頭にしておき、実

記号定数の変芯適用例

'define KAX..I即叡 128普define ZERO 普define T副 10

文字列を連結するコード

static void d田o3( )

char *句aTe凹xt = ·、alp凶ha/ゐb凶et回a/9但a皿aぷ";char aConv[118);

strcpy ( aConv , di videJ)y _separator( aText ,・/' ,3)); strcat(aConv,' ,.); strcat(aConv ,divide.-by_separator(aText , , l' ,2)) i

strcat(aConv,.,") ; strcat(aConv,dividej)yJleparator(aText ,' /' ,1));

-hIBi--la--lat--

‘‘E11'I・B'

・Ili--l

EE---ZEB--E---E-

char aArray[臥x..INDBXI;int aI;

for(aI = ZERO; aI < HALINDEX; aI++) ( if(aI == T副1{

/事インデックス t.!10のときだけ別処理事f/申...(省略)...申/

}else( procαl( aArray[aI)1 ;

無理に1行にまとめたコード

char 均,Text = "al~畑/beta/gamma"; char aConv[128);

strcat (strcat (strcat (strcat (strcpy (aConv , divide..by_separatorl aText , ' I ' ,3) ),.,") , divide_by_separator(aText , ' /' , 2)) ,'グ) , divide_by_se戸rator( aText, , /' ,11 1;

C MAGAZINE 2005 7 50

Page 10: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

l特|集121危険なコータ{ング癖を排除世

に耐えられるなら『よいソース jではない

か? J という人がいます。たしかに一理あ

りますが、せっかくオブジェクト指向プロ

グラミング言語を使っているのに、そのメ

リットをまるで生かしていないのと、ソー

スを引き継いだ人がオブジェクト指向を理

Table 3 間違ったオフジ工クト指向による症状

名称ー

解していた場合、その人に対して混乱を与

えたり、間違った判断をさせる可能性があ

るという点で有害なのです。たとえるなら、

せっかく自家用飛行機を持っているのに道

路の上を走るだけにしか使っていないよう

なものです。事情を知らない人は何か深い

症状

クラス=構造体錯誤症 クラスを構造体と勘違いしている

クラス=モジュール錯誤症 クラスをモジュールと勘違いしている

メンバ変数露出症 メンバ変数を平気でpublicにしている

クラス肥大症 1 つのクラスが肥大している。 1 つのクラスに複数機能を背負わせている

奇怪継承愛好症椛な鳩継承酔やフ方ラツゆク加仇 破壊齢を伴附う継僻承を鞭使つ冗ていゆる。意献昧

のない多重継承もある

|議i寺駅十d12つの襲例 店主導ィ"惑轟ム 二龍一樹ここでは、ダメソースが生まれやすい典

型例やその具体的な対応の紹介として、 2

つの事例を取り上げます。

1つ目は、何らかの資源やデバイスを開い

て(あるいは確保して)利用し、最後に閉じ

る(あるいは解放する)という事例です。た

だし、操作対象が3つある場合にどのように

コーディングするかで明暗が分かれる r30P

EN-3CIβSE対応Jの事例です。

もう 1つは、複雑な状態制御をするのに、

-複数のフラグを使う

.単一のフラグを使う

.上記とまったく別のテクニックを使う

で上手/へたが分かれてしまうという「状態

遷移対応Jの事例です。

いずれも具体性が低く抽象度の高い説明

のように感じてしまうかもしれませんが、

実際の現場では、この2つが発展した事例

によく遭遇します。

i 手窒雪 可γ三 J弓ぎr三ヨiヨ去ミミ;詩bふι与J人ぷミJ←己=羊言茎4主主-',云:主芸;:こヴ;I3や吋O凹p附3GしG郎:SE酎対応品駒J ミイ討議理t~~';3:~.1二,~,ら忍轡.~S'~It:'.:lある OSで、

理由があるのか、あるいは飛行機に欠陥が

あるのかと誤解するのですが、実は持ち主

が飛行機の飛ばし方を知らなかっただけと

いうオチなのです。

間違ったオプジェクト指向が原因となる

数々の症状(Table3) は、さまざまなへたな

コーディングを生み出し、ダメプログラム

の原因となりますっこれらは、すでに開発

現場でいろいろと害毒をまきちらしているa

ょうですが、これらの事例を紹介するには

本特集の誌面だけでは足りません。興味の

ある方は「オプジェクト指向編希望J と編集

部あてにリクエストしていただければ、あ

らためて紹介できる機会があるかもしれま

せん。

-デバイスXを開く API

・デバイスXを閉じるAPI

・デバイスXを制御するAPI

があり、その仕様はTable4に示すとおりだ

とします。また、チャンネル0のデバイスX

に通電するプログラムは、 list5~こ示すとお

りだとします。ここでチャンネル0-チャン

ネル2までの3つのチャンネルを通電するプ

ログラムを作るとどうなるかというのが問

題です。ただし、もう 1つ要求されるのは、

チャンネルOからチャンネル2までをすべて

開いたなら通電をし、どれか1つでも開かな

いなら、どのチャンネルも通電しないもの

特集2 危険なコーテsィング掘揮を排除せよ 51

Page 11: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

があって、開くべきチャンネルが増えてい

った場合、メソッドが長文化し、百文の入

れ子がどんどん増えてしましザ複雑な入れ子

愛好症jならではの弊害に陥る危険性があ

ります3

砂途中リターンを使う解答例

入れ子が深くなることを嫌い、なるべく

インデントを少なくしようとするプログラ

マは、Iist7のような途中リターンを使いた

ディング例ともなるのです。このあたりは

ソフトウェア開発のおもしろさであり、同

時にいやらしさでもあります。

砂入れ子を使う解答例

もっともすなおなパターンは、 Ust6のよ

うになるでしょう。これは、 flつの入り口

に対し、 1つの出口J という構造的なスタイ

ルを守った方法で、教科書の模範解答のよ

うなコーディングです。ただし、仕様追加

とします。

これだけの単純な問題ですが、解答例は

いろいろ出てきます。しかし、あえてどれ

が正解とは書かないことにしましょう。と

いうのも、状況が変われば、それぞれの解

答の特徴の違いに応じて長所や短所が変化

し、ある状況で有利なものが別の状況では

不利になることがあるからです。したがっ

a正、状況によって、それぞれの解答は上手

』コーディング例ともなれば、へたなコー

関数2分割と途中リターンの併用

むltωIand le[3] ;

副回dle[OJ ..句enDeviceJt ( 0) ; “iandle[lJ '"句enDeviceJt( 1) ; 凶姐lldle[~) .. OpenDeviceJC(~);

de即3Sub( aHandle) i if{aHandle[O] > O){

Cloa凶evic・x(凶andle[OJ) ;

if(aHandle[l] > O){ C 1 oaeDeviceJC (副聞dle[l)) ;

static void del103Sub{int iHandle(3))

if{iHandle[O) == O){ return;

}

if{iBandle[l] :,. O){ return;

if{iHandle[2J == 0)( return;

-3

0

岨凋U

渇u

10 v

e

--•-

a

令』S

Ft

E---E

,.,, •• 82

s

・・・・・・

5

.

.

.

,, •••••

‘ ... E・g

-a

・・

ι寸-a

'z'

置e

.

.

.

.

.

.

.

.

. ,., •.•• ‘.B

..... ,.8.

,e

e

・-

1 つのデバイスのみを制御する例

inta凪ndle =句閥均viceXlol;it句Band le > 0)(

C∞tr削除viceXl aHand le r "PONER 0四つ;Close胎viceX I必旭ndlel;

}

if(aHandle[2] > O){ CloseDeviceX(aHandle[2)) ;

入れ子

int 剖旭ndle[3J;

討包ndle[O] = OpenO由vic8X {O) ; if(aHandle[OJ > O){

討姐ndle[l] '" OpenOeviceX(l); it{ a1泊ndle[ll > O){

aHandle[2] ..句組踊viceX(2) ; if(a出ndle[2] > 0)(

Contro 1 DeviceX (凶包ndle[O] ,・問WER QH"I; Contro([淘viceX(ðHandle[l)"POWER QH"); Control Devic邑X(必旭川le(2) "P制限叩") ;

Cloa凶eviceX(副創ldI e[ 2 J);

CloseOeviceX(aHandle[l]) ; }

CloseDeviceX(aH�dle[O]) ;

}

Contro([泊viceJt {iHandle[O] ,"PO\IIBR ON"); ControI DeviceJt (iHandle[l] , "p側鼠 ON") ; ControI DeviceX!iHandle[2] , "P側鼠 ON・);

フラグ利用

途中リターン

int aHandle[3];

aHandle[O] = OpenDevicax(O); if{ aH創ldle[O] == O){

return;

int 凶泊ndle(3) ;

aHandle[ 日] =句enDeviceJt ( 0 ) ; 胡町dle[l) ,.句enDeviceX( 1) ;

副田dle[2) ..句enDeviceJt( 21 ;

aH胡dle[l] = OpenDeviceX(l); if{ a1包ndle[l] == 0) (

CloseDevicex(aHandle[O]) ; return;

if{ aHandle[ 日] > 0 ““fandle[lJ > 0 r.&必弘ndle[2J > 0) ( Contro 1 Devicex凶fandle[O]"問WKR ON・);ControI DeviceJC(aHandle[lJ , "POWKR ON"); Control 加vicex凶fandle(2) ,・POWKR ON・);

ωfandle[2] '" OpenOeviceX(2); if{必包ndle[2] =.. O){

Close加v!ceX{副andle[O]) ; Clo�DeviceX{aHandle[l) 1; return;

}

if{副回dle[O) > O){ Clo闘DeviceX (ωfandle[O)) ;

if(aBandle[2) >日)(C I oseDeviceX ( al抱ndle(2)) ;

if(aHandle[l) >日)(C 1 oseDeviceJt ( aHand I e [ 1) ) ;

Contro1 Devicex(aHand I e[ 0) , "POWBR 00"); ControlDeviceX(凶包ndle[l) , "PQWBR ON・);ControI DeviceX(aHandle[2J ,・P聞ER ON勺;

CI ロseDevicex{a出ndle[O J) ; CloaeDevicex{aHandle[l)) ; Clos曲evicex{ωfandle[2 J) ;

52 C MAGAZ町E 2005 7

Page 12: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

がります。メソッドの長文化は避けられま

せんが、それよりも困るのは、 1つの出口と

いうスタイルが守れないことによって後始

末を忘れる事故を誘発しやすpf途中リタ

ーン依存症Jならではの弊害に陥りやすい点

です。

出口、すなわちリターンが1つなら後始末

はそこにまとめて記述できますが、出口が

複数あると、そのそれぞれに記述する必要

があり、あまりにも数が多いとうっとうし

いですし、ケアレスミスも誘発しやすいの

です。実際のところ、クローズ忘れのパグ

が発生しやすいのが、このスタイルの欠点

であり、嫌う人も多いのです。

そこで、インデントを深くせずにクロー

ズ忘れも出さないようにする工夫として関

数を2分割し、「開く/閉じるJを上位関数で

してしまい、下位関数では気軽に途中リタ

ーンを許してしまおうという list8のような

スタイルが生まれます。実都、うと筆者は、

このパターンを利用していることが多いの

です。

砂フラグを利用する解答例

途中リターンの弊害はイヤだがインデン

トは浅くしたい、でも関数の分割はイヤだ

90toを利用する

I

l

l

i

-

-

1

l特|集12|危険なコー角ング癖を排除ぜI

Table 4 デバイスX用のAPI

API 機能

int OpenDeviceXCint iCh) iChで示すチャンネル(1 以上の整数)のデバイス×を開く。成功すれば1以上の整数のハンドル値が戻る。失敗すればOが戻る

void CloseDeviceXOnt iHandle) iHandleで示すハンドル値のデバイスXを閉じる

void ControlDeviceX(jnt iHan iHandleで示すハンドル値のデバイスXIこiCommandという命令を与dle, const char ホ iCommand) |える

という人の解答例として、 üst9のようなも

のがあります。

この解答例では、aHandleを実行の判断

フラグとして利用しています。今までの解

答例の中でもっともスマートなのがこれで

しょう。

惨go'匂を使う解答例

反則技( ? )だといわれるのを覚悟のうえ

で、 gotoを使う例を紹介しましょう。今さ

らながら fgoωを使うのはよいか悪いかJ論

争を蒸し返す気などありませんが[制、 go加

を肯定する意見の中には、エラー時の脱出

のみに限定するならgo加を使うと記述がス

ッキリすると主張する人もいます。たとえ

ばUst 10のような記述です3

「入れ子パターンJのように深い入れ子で

読みにくいソースにならず、「途中リターン

パターンJのような後始末を忘れるケアレス

ミスも誘発しにくいのが、この記述の特徴

です。a・・、

とはいうものの、今の時代にあえてgoω

を使うメリットがあるかはきわめて疑問で

はあります。

[涯7]筆者は『すべてのプログラマがよいgotoと悪いgotoの見分けがつくなんて理想的な状況などありえないから、一律禁止したほうが無難だJという、消極的な理由ではあるが、 goto否定派である

砂例外を使う解答例

go切を使うのに抵抗がある人が、エラ一

時の脱出に例外を使うという例もあります。

これは、残念ながらC言語では使えません

が、 C++やJava、 C#、 VB .NETならいずれ

も利用可能です (list 11) 。

ただし、例外とはあくまで例外的な事象

にかぎって利用すべきものであり、通常処ー

理でも使いまくってしまう「例外処理錯誤

例外を利用する(C++)

むlt 針包ndle[3] ;

剖匂nd1e[0] = 0; ωfandle[l] = 0; aHandle(2) " 0;

aHandle[O) = Open加viceX(O) ; if(副andle[O) 回日) goto BRRORi aHandle[l) "句g虫色viceX(l) ; if( aHandle[l) 目。) goto BRRORi aH創ldle(2) =句回DeviceX(2) ; if(aHandle[2] ;:= 0) goto BRRORi

Contro 1 DeviceX ( aHand 1 e[ 0] ,・POWER ON・ )iControJDeviceX(aHandJe[l] ,・P側ER OH・ )iContro 1 DeviceX(aHand 1 e(2) ,・p側ER OH・ )i

ERROR: if(al包且dle[O] > O){

C I oseDevicex ( aHa且dle[O J) i

if(aHandle[l) > O){ Clo回DeviceX(必弘ndle[l]) ;

}

if(aHandle[2) > Ol{ C I oseDeviceX ( aHand 1 e [ 2] ) ;

try{

aHand 1 e [ 0] " OpenDeviceX ( 0) i if(剖land1 e[ 0] == 0) throwi aH副dle[l] = OpenDeviceX(l) i if(剖旭川 le[11 四 0) 廿ll"OWi副回dle[2] " OpenDeviceX(2); if凶旭川 le[2] a= 0) throw;

Contro1 DeviceX (al旭川 1e[0 ],-問問R ON") j

Contro 1 Devi個以必旭川 le[l] ,.POWER OH勺;Contro1 DeviceX(aHand 1 e[ 2] , .POWER ON勺}

catch(... )(

if(aHandle[O) > 0) ( CloseDevic・X( aHandle[O])i

}

if(砥泊OOle(1) > O){ CloseDeviceX(aHandle[1)) ;

if(aHand1e[2) > 0)( CloseDeviceX(aHandle[2]) ;

特集2 危険なコーディング療を排除せよ 53

Page 13: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

症Jに陥らないように注意しましょう。ここ

で示したUst 11の例では問題ないとは思い

ます。

砂finallyを使う解答例

最近よく目にする白lallyを使う例も紹介

しておきましょう。

これも、残念ながらC言語やC++では使え

ない技ですが、 Java、 C#、 VB.NETならいa・・‘ 含れも利用可能で、慣れるとおそらく、こ

れが最強の記述例になるような予感がしま

す (Ust 12) 。I ~ ,.…‘皆同 ち智"',~~~詰

}時遷移対応 士J 騨骨三三 雲寺

冒頭で紹介したUstlのように、 Case文が

多いために長文にならざるをえないという

状況では、最初から短い行数のメソッドを

作ることをあきらめてしまうケースがあり

finallγを利用する (Java)

public void de舶() (

int[) aH聞dle " n,側 int (3) i try (

aBandle[O) =句措nDevi国x(OI i if(al旭ndle[O] == 01(

returni

副担OOle[ 1] =句輝nDeviceX(11; if(aHandle[l] == 01{

retuロ1;

必治OOle[2) =句組踊vicex(2) ; if(aHandle[2) = O){

return;

ControIDevicex(aHand le[O) , 'p倒ER ON勺;ControlDeviceX (al姐ndle[l) , 'p倒l!R ON勺 iControl DeviceX l必祖国 le[2 l, 'PO舷鳳 ON勺 i

fillAlly ( if(al包ndle[O) > 01 (

CloseDeviceX(aHandle[O)) ;

if(al姐ndle[l) > O}( Clo踊Devi曲以ωfandl骨 [111 i

if(alfandle[2) >日)(C 1 oseDevlceX I al泊ndle[2)1;

ます。ところが筆者が見るところ、どうし

ても switch文(Visual ßasicならSelect-Case

文)に頼らざるをえないわけではなく、ちょ

っとしたテクニックで逃げられるのに、 swi

tch文なら「書きやすpJので、あえて「読み

やすL リソースをあきらめている例も多々見

られます。

①rAJ を読み込んだら、デバイスAに対す

る出力予告とする。 rSJを読み込んだ

ら、デバイスBに対する出力予告とする

②rOJを読み込んだら、デバイスYから 16

バイトを読み取り、①の事前出力予告

で指定されたデバイスに対してそのま

ま送るものとする

とくに状態遷移がからむものに議箸なので、ここでは例題を示して徐々にソースが

変貌して L 、く様子を紹介しましょう。

③最初にデバイスYから読み取れるのは、

rAJrSJのいずれかであり、この2つ以

外がきたらエラー扱いとする。またrAJ

rSJのいずれかがきたら、その次はrOJ

でなければならず、 rOJ以外ならエラ

ー扱いとする。エラー状態になった場

合はデバイスYを空続みする以外は何

もしない

砂例題

ある osでデバイスYからデータを読み取

るAPI、およびデバイスAとデバイスBにデ

ータを送るAPIとして、 Table 5に示すもの

が用意されているものとします。デバイス

Yからは、 1ノ fイトずつデータを読み取りま

すが、このとき以下のような通信がきてい

るとしますっ

④最初の出力を終えたあと、デバイスY

から読み取れるのは、 rAJrSJrOJのい

ずれかで、この3つ以外がきたらエラー

扱いとする。 rAJrSJなら①に書かれた

単純にコーデイングした例(C++)

加。 I aBrrFlag .. false; 11エラー状態繊出int aOutDevice i 11出力デバイスの選択int a民ount加wn = -1; 11-2なら A , B , O待ち, -1 なら A , B待ち,

110ならD待ち, 1以上ならDのあとの綱渡しfor(i;1{

int aDat = ReadDeviceY( 1 i if( I aErrFlag 1(

if( aDCount加1m == -2)( if(aDat == '1.' 11 曲at == 'e' 1(

aOutDevice = aDati a民ountDown " 0 i

}elae if(aoat = 'o'){ a民白untDown " 16;

}elae( aErrF 1 ag = true;

}

}else ifla民ountDown ==・1){if(aDat = '1.' 11 aDat =" 'e'}(

aOut:!levice " aDat i aDCountDown " 0;

}elae( aBrrFlag '" truai

}

}else if(a民ount以)WIl ==日)(if(aoat == 'D'}(

aDCo凶tDown = 16; }elae(

aErrFlag = truo; }

}else if(凶犯'ount旬開 >0 1(iflaOUtDevice == 'A' 1 (

WriteDeviceA (static_cast<\lll8ign・d char> (aoat) 1; }else if(aOUtDevice == 'B'I{

writ凶eviceB (static_cðst<unsi仰ed char>(aoat)); Table 5 告デバイスに対するデータの読み書きを行うAPI}

API

int ReadDeviceYO

void WriteDcehvair cieDA a (unsigned char iDat)

void WriteDeh vir cieDB a (unsigned char iDat)

54 C MAGAZINE 2∞5 7

機能

デバイスYから1Hイトのァータを読み取る。ただし、読み取れない場合は内部で読み取れるまで待つ

デバイスAに 1)\イトのデータを送る

デバイスBに 1 バイトのデータを送る

if (--aDCountOown == 0) ( aDCountDown .. -2;

Page 14: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

..........--

|特|集12危険なコー科ング癖を排除ぜI

Fig.4 状態遷移図

/¥ :主-叩川...よ~守Lぐリザ

‘ にニ-.冶t勺:-:-下7記;毛三z丞d三三三乙玉しz

〉λ\B

D

務認

議曹 司f.N.昨日

1つのswitch文に変更した例(C++)

曲四位。cStatus { PS_START , 11開始直後の状態l次にくるのはA , B、それ以外の入力をUめない}PSJ>EVJ , IIAがきた直後{次にくるのはD、それ以外の入力を認めない}PSJ>EVJ!, IIBがきた直後{次にくるの l如、それ以外の入力を認めない lPS_Pt1l'J , IIA-Oがきた (16バイトを欄涼し、そのあとにくるのはんB , O)PSJ'llTJ!, IIB→Dがきた (16バイトを繍流し、そのあとにくるのはA , B , olPSJmROR 11エラー状娘、空麓みを続ける

static void demo2 ( )

ProcStatus aProcStatus = PS_START; int aDCounter; for( ; ;)(

int aDat = ReadDeviceY(); switch( aPr∞Status){ case PS_START: 1 tr期始直後の状錨

switcb (a.Dat) ( case 'A': aProcStatus = PS..DEV_A; break;

case 'B': aProcStatus = PSJlEVJ!; break;

default: aProcStatus = PSJRROR; br倒k;

break;

case PSJlEV..A: I/Aがきた直後if(aDat== 'D')(

とおりに処理し、 fDJなら②に書かれ

たとおりに処理する

この規則を単純にコーディングするとUst

13のようになります。

ただし、 Ust 13を見ても、これが正しい

のか間違っているのかはすぐには判断でき

ません。また、軽微ではあるものの「長文愛

好症Jf複雑な入れ子愛好症Jの症状が見受

けられます。このソースをデバッグしたり a・

仕様変更や仕様追加するのは、ちょっと棒

路します。たとえば仕様追加として、

• fCJがきたらデバイスCへの出力予告とみなし、 fEJがきたら 32バイトの横流

しをする

という条件を加えて、このソースを変更し

てくれといわれたらどうでしょうか? 凡

庸なプログラマなら、いつものようにコピ

ー&ペーストで対処するのかもしれません

が(笑)。

惨状態遷移変数を導入して 1 つのswitch

文にする

少しだけ頭のよいプログラマは、 Ust 13

の問題点として、複数のフラグを操ること

がプログラムをややこしくする原因だと見~

抜きます。

Ust13で、はaE町Flag、 aOutDevice、aDCo

untDownの3つのフラグでプログラムの流れ

を制御しているようですが、これらの制御

フラグを一本化できないかと考えるでしょ

う。また、aDCountDownは本来は横流し

するバイト数のカウンタに専念させるべき

なのに、状態遷移にも併用しているのがよ

くないことにも気づきます。

そこで、それぞれの変数の役割を、

• ProcStatus .…・・状態を示す列挙型

• aProcStatus …状態を保持する変数

• aDCounter ……横流しの残りバイト数

を保持する変数

に変更し、きちんとFig.4のような状態遷移

図を書き、複数あった江文の記述を 1つの

switch文に変更します。そのようにした例

がUst14です。

ところがIist 14は、最初のUst 13よりも

特集2 危険なコーディング祭を排除せよ 55

Page 15: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

長文になっていますし、よけい視雑で読み

にくくなっているように見えます。仕様変

更や仕様追加に関しては最初のソースより

もやりやすくなっているはずなのですが、

ちっともそんな印象を与えてくれません。

何がいけなかったのでしょうか?

本特集でも何度も述べたとおり「長い行

数はそれ自体が頭脳に対する暴力であり『読

.,.",1;.やすさ j という点では十分に失格」なので

そのため、せっかく工夫をしたのに「こ

れって、典型的な『策士が策に溺れたjだよ守申

ねJ と姉撤されることになります。おまけに

「コピー&ぺース卜愛好症Jを発症していま

すし、 rswitch文肥大症J も併発しています。

砂関数分割による改良

1つの関数が肥大する「長文愛好症Jの対

策は関数分割をすることです。また、直値

を使っている部分(üst 14,⑤の 16と書いて

pる部分)もシンボルに置き換えます。さら

に「コピー&ぺースト愛好症j を減らすため、

共通部分を洗い出して整理すると üst 15の

ようになります。

�t 13やüst 14に比べるとずいぶんスッ

キリしたことがわかるかと思います。これ~

z ら仕線変更や追加がやりやすくなったこ

ともわかるでしょう。

砂関数ポインタによる改良

しかし、関数分割というアプローチでは、

仕様追加によって状態が追加されるたびに

どんどんCase文が追加されていく「ケース

ラベル多発症Jが予防できません。そこで

�t 16に示すように関数ポインタを使って

肥大しそうなswitch文を廃止してみましょ

う[制。

どうでしょうか? 最初のソース (üst

13) と比較するとまったくのべつものに変貌

してしまい、そのことに驚いた読者もいる

と思います。これぞまさしくプログラミン

グの醍醐!昧であり、プログラマが工夫をし

ようと思えば、いくらでも工夫できる余地

があるのです。そして工夫することで今ま

で「汚いプログラムだ、へたなコーディング

56 C MAGAZlNE 2005 7

マ巳

aD白unter = 16; aProcStatus = PSJ'UT_Aj

}else( aProcStacus = PS_ERRORi

brωlci

case PSJlEV_B: /Isがきた直後iflaoat == '0'1(

a即ounter = 16j aProcStatus = PS_PUT_S j

}else( aProcStatus = PS_ERRORi

brealtj

case PS_PUTJ,: I/A→Dがきた 116バイトを償流し lif( a即0叩ter> O){

--aDCounter i WriteOeviceA (static_cast<山lsigned char>laoatl) j

}else( 開itch( aDatl(case 'A': aProcStatus = PSJ)8VJ, i breaki

case 'S': aProcStatus = PSJ)EVJlj brealc;

case '0': a民飢mter = 16; brealtj

default:

breaJcj

aProcStatus = PS_ERRORi breakj

国se PS_PUTJI: IIB→Dがきた(16バイトを繍流し}ifla民ounter > O}{

ー-aDCounter;

WriteDevlceBlstatic_cast<unsigned char>(aDat)) j }else(

switch(aDat) ( case 'A':

aProcStatus = PS_08VJ.j brealt;

case 'B': a?rocStatus = PS_08V_Bi brealti

case '0': aDCount阜r = 16; brealtj

default:

br回lcj

aProcStatuB = PS_ERRORi brealc;

case PS_ERROR: / /エラー状態、空襲みを続けるbr阻lcj

default: 1/ 1本来 1 ;1: ζζにくるはずがない}1/( デバッグ時にエラー検出できるよう、ここにブレイクポイントを/1 置くなどするとよい}

brealti

関叡分割による改良(C++)

struct ProcControl ( ProcStatus lIPS j /1状態墨移int l1li:紅白unteri 11織流しバイト数

static const int D_TRANSFERJI'tTES = 16i 1/繍流しさせる総バイト数

…(;

-(;

-・・①

-・・①

Page 16: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

List 15

「7V

sta山 void ?rocStart (Proぬntrol&: io見出加t)

switch(iDat) ( case 'A':

io?C.mPS = PS_D町..A;break;

case 'B': io?C.mPS = PSJ)EV~; break;

default: i口PC.皿PS = PS_思ROR;

break;

static void ProcDevAorB(ProcControl& iロPC , int i Dat ,ProcStatus iNextStat) {

if(iDat ==γ)( ioPC.mDCounter = D_TRANSFERJIY'l'3S; iopc.DlPS = iNextStat;

}else( ioPC.mPS = PS..KRROR;

typedef void (事WriteOevicePPtr) (unsi伊ted ch紅 1;

static void ProcPutAorB(ProcControlr. ioPC,int iDat , writ曲臥ficeFPtr i枇iteDev l

if (ioPC.m配ounter > 0) (

--iopc. mDCounter; iWriteOev( static_cast<unsigned char>( i民主));

}else( switch(i恥t)(case 'A':

ioPC.mPS = PSJ>BV..A; br阻k;

case 'B': ioPC.阻PS '= PSJlEV~; brealt;

case '0': ioPC. 凶lCounter = D_TRANSFERJri'l'ES; break;

default: iopc. mPS = PS_ERROR; break;

static void dell¥03

ProcControl a..PC; aPC. mPS = PS_START;

for! ;;)( int aDat = ReadDeviceY(); sW'フtch(aPC.mPS){ case PS_START: /1開始直後の状態

ProcStart<a.PC,aDatl; break;

case PS_DEV_A: J/Aがきた直後ProcDevAorB( aPC, aDat ,PS..PUT_AI i break;

c�e PS_DEV~: / /Bがきた直後ProcDevAorB( aPC,aDat , PS_PU'l'~) ; brealt;

case pS_P町四A: I/A→Dがきた (16バイトを横流し lProcPutAorB( aPC , aDat , Wri teDeviceA) ;

breakj

C臼e PS_PI1I'~: IIB→Dがきた (16バイトを繍流し}ProcPutAorB(aPC ,aDat ,WriteDeviceB l; br回k;

case PS_RRROR: /1エラー状態、空読みを続けるbreak;

default: bre必t;

圃劉危険なコ}ヲ{ング癖を排除引

だ、でもしょうがなpJ とあきらめていたこ

とが解消するはずなのです3

[注8]今回は試してみなかったが、オブジェク卜指向プログラミングを導入して、デザインパターンでいうところのStateパターンを試すと肥大しそうなswitch文を廃止できる

[参考文献】 |

最後にへたなコーデイングにからんだ文

献の紹介をしましょうりこの手のテーマ、

すなわちコーディングレベルでの美しP/汚

pを論じたものは案外見つからず、たまに

見つかっても分量が少ないので、まとめて

読んだり考察する機会が少なすぎるように

思えます。そのため、現場にはあんなにも

論外なコーディングが満ちあふれでいるの

かもしれません(笑)。

しかし、少ないながらもなかなか読み応

えのある文献が多いのが救いでしょう。

[1] rCプログラミング診断室j、藤原博文、

技術評論社、 ISBN4・n41 ・1787-0 、 ht

tp:llwww.pro.or.jprfuji/mybooks/cd

iagl

ネット上で検索するとまっさきにヒット

a・

するのがこれです。文字どおり「汚PC言語・

ソース jを取り土庁、どこがどうダメなの

か、どうすればいいのかを診断しています。

ただし、実際の現場では、この本に載って

pるソースですら美しく見えてしまうほど

悲惨なものが珍しくないと思うのは私だけ

でしょうか(笑L さらに、近年は間違った

オブジェクト指向による、より論外で最低

な自称オブジェクト指向プログラムが出回

っていて、そのたびに頭を抱えてしまうの

で、ぜひこの本の作者には続編としてオブ

ジェクト指向編を書いていただきたいと希

望します。

[2] r トラブルを避けろ! プログラミン

グの禁じ手一一C言語編j、真紀俊男、

ソフトバンク I~ブリッシング、 CMAG

AZINE Vol. 12 No. 4 pp. 21 ・ 49

1つ 1つの項目の分量が少ないので読みや

特集2 危険なコーディング容を排除せよ 57

Page 17: 聞と問の定義 - 関西学院大学ishiura/sd/note/cmagazine-2005-07.pdf · a・・‘ 宮坂電人  ソースの悪い部分がどうしても自につくようになります。

すいかと思います。ここに書かれているこ

とを守るだけでもかなりソースの品質は上

がるのだろうと思います。しかし、簡単に

は守れないから、あんなにも現渇には悲惨

なソースがころがっているのだろうとため

息をつかざるをえません。ちなみに、 C++

編は、 C MAGAZINE Vol. 12 No. 7 pp. 12-38

です。いずれも現在発売中の「まるまるc

duGmNE-2000年度版ーJに収録されて

ミます。 また、 http://www.cmagazine.jp/

srcj恒njite/c/index.htmlで、記事の抜粋が公

開されています。

[3] rアンチパターンーソフトウェア危篤

患者の救出』、 William J. Brown 、 Rap

関数ポインタによる改良 (C++)

struct ProcWorks;

融画危険なコ}ポ↑ング癖を排除改

hael C. Malveau 、 Hays W. McCormic

k 川、 Thomas J. Mowbray、ソフトパ

ンクパブリッシング、 ISBN4田7973-

2138-5

コーディングレベルよりもマクロな観点

から、ダメなプログラムが生まれるパター

ン、すなわちアンチパターンを論じた名著

です。

本特集ではダメなコーディングが生まれ

る背jii-や対策についても論じたかったので

すが、ページ数の制約などによってかない

ませんでした。ダメなコーディング対策は

コーディングの品質を向上することはもち

ろんのこと、それ以外でもいろいろ工夫を

しなくてはならないのですが、そのあたり

List 16

はこうした本を読むことで補完していただ

きたいと思います。

[4] rコードコンプリート第2版j 、 Steve

McConnell、日経BPソフトプレス、

(上巻)ISBN4-891 00-455・x、(下巻)IS

BN4-89100・456-8

最近になって第2版が登場しましたが、

質、量ともにかなりレベルが高く実践的で、

職業プログラマはもちろんのことプログラ

ム開発をしたい人すべてが読むべき本です。

コーデイングレベルはもちろんのこと、あ

との工程や開発全般にわたる具体的なノウ

ノ、ゥが詰まっており、買って損はしない名

著だといえます。

typedef void (*ProcFPtr)(ProcWorks&l; 1/ ←関数ポインタの導入static void 釦回utAorB( Pr民国orks& iOÌ'lorks , Writet冶viceFPtr iWriteDevl [

58

struct ProcWorks { ProcFPtr mProcFunc j

int mDataj int m配ounteri

}j

1/処理問霊童(状鍍遷移の保袴も兼t.lる)11入力データ保持11機流しバイト数

static void ProcPuncDevA( Proc怖orks晶 ioWorksl;static void ProcPuncDevB( Proc帽orks& io蜘rks)jstatic void ProcFuncPutA (ProcWorkS& ioworks) j

static void ProcPuncPutB(ProcWorks& ioWorks) j

static void ProcFuncError! ProcWorks& ioWorks I ;

11開始直後の状態(次にくるのはんB、それら以外の入カを認めない}static void ProcFuncStart (ProcWorks& ioWorks)

日....itch ( ioWorks . mData) { case 'A': ioWorks. mprocFunc = ProcFuncDevAj break;

case 's': ioWorks.mProcFunc = ProcFuncDevBi break;

defau!t: ioWorks. mPr田F四c = ProcFuncError; br回kj

IIProcFuncDevA , ProcFuncDevBの下繍けstatic void SubDevAorB (ProcWorks& ioWorks , procFPtr iNextStat I [

if(ioWorks.mData == '0') (

ioWorks. mDCounte:: = 0_ TRANSFER..BYTES j

ioWorks. mProcFunc " iNextStat; }else{

ioWorks. mProcFunc " ProcFuncError;

IIAがきた直後 l 次にくるのはD、それ以外の入力を認めない}static void ProcFuncOevA (ProcWorkSl< io'llorks I

SubDevAorB (ioWorks , ProcFuncPutAI ;

/lBか.きた直後{次にくるのはD、それ以外の入カを腿めない lstatic void ProcFuncDevB (ProcWorks& ioWorks)

SubOevAorB( ioWorks , ProcFuncPl比B) ;

IIProcFuncPutA, ProcFuncPutBの下腕け

C l¥lA.GAZIXE 2005 7

if (io'lIorks. mDC以mter > O){ --io\llorks. mDCo叩ter;iWriteDev( static_cast<UllSign凶巴bar> (ioWorks.mDatal) ;

}e!se{ switch( ioWorks. mDatal { case 'A': ioWorks.lIProcPunc " ProcPuncDevAj br曲k;

case 'B': io¥llorks. mProcFunc " ProcPuncDevB j

br回kj

case 'D': ioWorks .1DCounter " O_TRANSFERJ!YTES; break;

defau!t: ioWorks.lllProCPunC = ProcF'.mcError; breaJci

IIA→Dがきた (16/\イトを4量流し、そのあとにくるのはA , B , OIstatic void Proc?uncPutld ProcWorks& ioWorks)

SubPutAorB( ioWorks , WriteOeviceAI ;

IIB→Dがきた (16バイトを繍涜し、そのあとにくるのはA , B , O)static void ProcFuncPutB (ProcWorks& io'lIorks)

SubPutAorB (ioWorks , WriteDeviceB 1;

11エラー状態、空緩みを続けるstatic void ProcFuncError! ProcWorks& ioWorks I

11(1可もしない}

static void demo4( I {

ProcWorks aWorks; aWorks . mProcFunc = ProcFuncStart; for!; ;)(

aWorks .!I'.Data = ReadDeviceY( 1; a)/orks.lIlProcFunc(aWorks); 1/←switch文が消滅

-•