真紀俊男の ローテク講座 プログラミングレッスン Ja a 言語 ? ログラミング Java 言語を始めよう B5 変 376 ページ ISDN 4-7973-0803-6 本体価格 2,400 円 洋 a 言語 プロクラミング、を をレッスン 0 、第 , 、いをし ( 。第 pvsprintf void pvsprintf(char *outBuf,size—t inSize,const char *inFormat,va—list inV) char *theLimit = outBuf 十 inSize - whiIe(*inFormat & & outBuf く theLimit){ if(*inFormat = = switch(*(inFormat + 1) ) ( outBuf = p—Char—Buf(outBuf,va—arg(inV,int) inFormat 十 = break; case 'd' ・ outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(inV'int) , 10 inFormat 十 = break; case 8 outBuf = P2—Text—Buf(outBuf,theLimit,va—arg(inV,char * ) inFormat 十 = break; case X outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(inV,int) , 16 inFormat 十 = break; a 言語を始めよう ava 言語プロ ? ラ当ングレッスス上 ) 結城 めの語ス門書。体書 ( よ巻 ) : プログラを冫の知識や経験のない初心斉 を料象にア av case ・ *outBuf 十十 = ' 宅ー inFormat 十 = break; default: *outBuf 十十 = *inFormat 十十一 break ー )else{ *outBuf 十十 = *inFormat 十十一 *outBuf = 0 後に残しておいたわけであるが。単に P2sp ⅱ n を考え , 次のような引数を考える。 void P2sprintf(char * outBuf, va list th eVA , を消し , 外部からパラメータを得るように Size t inSize, するだけで , ほとんど P2sp ⅱ n げと差はない。 const Char * inFormat, List 10 のようになる。 ⅲ Size は最大格納サイズで , これを超え [ 注 ] て文字列の格納を行わないものとする。 [ 1 ] スボコンが嫌い 制限を付けるには , あらかじめ制限量を 整数の変数で持たせる考えもあるが , C 言 亡くなった手塚治虫氏という有名な漫画 語ならではの考えで , ポインタの比較によ 家もスボコンが嫌いで , なぜこういうのが って制限を付けようと思う。というのも実 ウケるのか理解できず , 周りの人に聞いて 際にやってみればわかるが制限量の変数を 回ったというエピソードが残されている。 持たせた場合 , 文字列へのポインタとは別 その話を聞いて , 筆者は手塚治虫氏が大好 個に制限量の判断を同時にやらねばならず きになった。 プログラムがややこしくなるからである。 [2]ctype. h あれこれ説明するより実際のコードを見せ ただしいまどきのコンパイラは C + + 兼用 よう (List9)0 山 eLimit がミソである。おわ が多く , 新しい規格に従っている場合 , cty かりいただけるであろうか ? pe. h ではなく cc 甲 e にある。これにかぎら va-list 対応 ず . h というファイルを調べると内部で , #include <CXXX> 残った課題は実に簡単である。だから最 となっていることが多い。 オ ? ジ ; 暴指向を始めよう Ja 言語プロ ? ラミングレッスン ( 下 ) 結城浩著 結城浩・ = よプ 0 ~ 崧ーツ初者のための の知識を踏まえ , クラス、クラスの継承、例外。 以レッド、バッケー ~ など、オブジェクト指。 の概念を、多ぐのサノブルプログラムを使っ。て、 明快に解説している。を三イをえイ 虹好評発売中 ! SOFT ソウトバンク穴ブシング株式会 BANK " http://books.s Oftba nk 上 0. / ublishing EL : 04g6 2- ・加 0 示価格は税別 ) B5 変 344 ページ ISDN 4-7973-1010-3 本体価格 2400 円 真紀俊男のローテク講座 137
ぎに割算をし , 1 桁ずつを切り出してバッ フアに書き込んでいる。 1 桁の数値を 1 文 字に変換するのに市 eN という配列を使って いるが , こうするほうがプログラムコード がスッキリし , しかも速くなる。高速なデ ータ変換をしたいときに , あらかじめ固定 配列を用意するのは , よく愛用される手法 である。これを知らないで , List 7 のような 感じでプログラムを作る人もいる。もちろ ん間違いではないが , ほかにも方法がある ことを知ってほしいと願う。 「ほかにも方法が・・・・・・」といえば , d ⅳとい う系列の関数を使う方法もある ( List8 ) 。 れは割算の商と剰余を同時に求めて市 v ー t と いう構造体に答えを返すものである。 機械語レベルの話で恐縮だが , 割算命令 で同時に商と剰余が 1 命令で求められる cp U があるが , C 言語のプログラムでは同じ 割算になるのに , 商と剰余を求めるために 2 回同じ割算をするのがもったいない ( ちょ っと , みみっちいかもしれないが ) のを気 にして , この div が作られたのではないかと 想像する ( 真相はどうかは知らない ) 。 c = div(a,b) とした場合 , c. quot に a+b の商 , c. rem に剰 余が得られる。ただ , 実際問題 , ここで示 したような例では本当に速度アップが期待 できるものかどうか疑問ではあるが。 P-IntN-Buf-Sub の別解答例 制限を付ける処理 こで , やり残しているのはふたつあり , あり , ー vsnp ⅱ n げの代用品が最終目標である。 こまでのプログラムは , まだ試作品で ②制限を付ける ( 1 )va_list 対応 136 C MAGAZINE 20 4 ここで Psprintf の制限付きバージョンで からである。 いと思う。というのも ( 1 ) は案外 , 簡単だ た変形パターンで , 先に ( 2 ) から片付けた いが , いつものローテク講座のチマチマし となる。いっぺんにふたつを片付けてもい 0 P 」 ntN_Buf_Sub の別解答 return ioBuf ・ *ioBuf 十十 = *thePtr ・ while(--theptr 》 = theBuf & & ioBuf く inLimit){ thePtr = P-IntN-Buf-Sub(theBuf,inNum,inRadix); inNum * = - 場 *ioBuf 十十 = ' - ' if(inNum く 0 ) ( char 社 he て・ char theBuf[161; static char *P2-IntN-Buf(char *ioBuf,char *inLimit,int inNum,int inRadix) return ioBuf ・ *iOBuf 十十 = * 土 nT 十 whiIe(*inT & & ioBuf く inLimit){ static char *P2—Text—Buf(char *ioBuf,char *inLimit,char *inT) va—end(theVA); *outBuf = 0 ' *outBuf 十十 = *inFormat 十十一 )else( break; *outBuf 十十 = *inFormat 十 default: break; inFormat 十 = 2 ー *outBuf 十十 = ' ' case ' inFormat 十 = 2 ・ outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(theVA,int) , 16 case X break; inFormat 十 = 2 ・ outBuf = P2-Text—Buf(outBuf,theLimit,va—arg(theVA,char * ) case S inFormat 十 = outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(theVA,int) , 10 break; inFormat 十 = outBuf = P—Char—Buf(outBuf,va—arg(theVA,int) case C “ ch ( * ( i 0 て m 北十 1 ) ) { if ( * 土禛 orm 北 = = ' ) { whiIe(*inFormat & & outBuf く theLimit) ( va—start(theVA,inFormat); char *theLimit = outBuf 十 inSize - va 」に theVA; void P2sprintf(char *outBuf,size—t inSize,congt char *inFormat ) P2sp 「 intf return 土 oBu *ioBuf 十十 = theN[inNuml; inNum = theD. quot; *ioBuf 十十 = theN[theD. reml; theD = div(inNum,inRadix); whiIe(inNum > = inRadix) ( div-t theD; static char theN[ ] = 第 0123456789ABCDEF を static char *P-IntN—Buf—Sub(char *ioBuf,int inNum,int inRadix)
五ロ = 編 の 0 ◆ 個人が趣味でやっている場合もこれらの 調べると「禁じ手パターンの宝庫」と化して 問題は起こりますが , ある程度以上になる います。この手のソフトのソースが公開さ ひとつの関数の行数が多すき、る と勝手に自滅したり , 「飽きてしまいまし れることはあまりありませんが。そういえ 深刻度☆☆☆ ( 重度 ) た。終了です」と降参して , それ以上は増え ば某 Web プラウザがオープンソースになっ ません。しかし , そういう自滅や「飽きてし [ 症状 ] たときに少しだけソースを見たのですが , まいました」が許されないプロは深刻です。 やはり「分量に関する禁じ手パターン」の宝 プログラムの解読や改造が困難になりま プロだから , どんなにイヤでも仕事はや 庫でした。オープンソースのメリットを生 す。また , バグが発生しても退治しにくく らなくてはならないのです。そのため , ソ かすどころか , 全然生かせない実例となり なります。 ースがどんなに読みにくくなっても , 際限 つつあります。それはオープンソースとい [ 原因 ] なく分量が増えていくという運命が待って 多くの場合 , 怠慢が原因です。すなわち , うスタイルのよし悪しという問題ではなく , います。しかも , このような肥大化したプ 分量に関する禁じ手パターンにより , もは 適当にプログラムを入力する習慣が付いて ログラムは自分ひとりで開発した場合でも や誰にも手を付けられないような惨状を呈 いるものと思われます。 わかりにくいことが多いのに , まったく赤 [ 対策 / 予防 ] しているわけです。なかなか新しいバージ の他人が作ったプログラムとなると , わけ ョンが現れなかったのも当たり前で , あれ 実はどう対策 / 予防していいのか筆者に がわかりません。そこで , そのソースを受 に手を付けられる人がいるなら , そうとう も思いつきません。職業プログラマの場合 け継いだ人はなるべく既存のソースをいじ 勇気があるか , かなりのパワーがある人で 1 行増やすたびに , いくらか給料をカット らないで「接ぎ木」をし , ますます分量が増 しよう。それだけの勇気とパワーがあれば , するというのはどうでしようか ? 新規にプログラムを作り直すほうが手つと [ 例外 ] えていくわけです。 最近の巨大ソフトが , 「機能は豊富だが り早いような気もします。 試作段階や試行錯誤している場合は許さ [ 注 3 ] ひとりで開発するのではなく , 複数の バグだらけ」という現実は , すでにみなさ れるかもしれません。しかし , 本番ではキ プログラマが共同してプログラムを開 んがご存じのとおりです。実際に , なかを チンと関数を分けて整理しましよう。 発するスタイル ひとつの関数の行数が多い例 VOid psprintf(char *outBuf,const Char *inFormat,. va—list theVA; int thel; char *theCp; char theCb[161; static char theN[ ] = ” 0123456789ABCDEF 第 va—start(theVA,inFormat); while(*inFormat) { if ( * inFormat switch(*(inFormat 十 1) ) ( case C thel = va—arg(theVA,int); if(isprint(thel) ) { *outBuf 十十 = (char)thel; }else{ *outBuf 十十 = inFormat 十 = break; thel = va—arg(theVA,int); if(thel く 0 ) ( *outBuf 十十 = thel * = -1 ・ theCp = theCb; while(thel > = 10 ) { *thecp 十十 = theN[theI 10 thel / = 1 の *theCp 十十 = theN[theIl; while(--thecp > = theCb){ *outBuf 十十 = *theCp; inFormat 十 = 2 ー break; case S thecp = va—arg(theVA,char * List while(*thecp){ *outBuf 十十 = *theCp 十十一 inFormat 十 = break; case X thel = va—arg(theVA,int) : if(thel く 0 ) { *outBuf 十十 ' 、 thel * = ” 1 ー theCp = theCb; while(thel > 16 ) { *theCp 十十 = theN[theI 宅 16 thel / = 1 研 *theCp 十十 = theN[theI]; while(--thecp >= theCb){ *outBuf 十十 = *theCp; inFormat 十 = 2 ー break; *outBuf 十十 inFormat 十 = 2 ・ break; default: *outBuf 十十 * i れ FO て ma に十十一 break; )else{ *outBuf 十十 = *inFormat 十十一 *outBuf = ' 0 ' va—end(theVA); プログラミングの禁じ手 35 特集 1
で , 大規模なチームプログラムをやってい 構造化が進んで , 保守性の点でもプログラ [ 備考 ] ムの質が向上します。 switch 文を使った場合 , case ラベルが大 るところでは , たいして珍しくなかったり 量にあると , いとも簡単に行数が多くなり します。 共通部分をまとめない こういう巨大関数ができあがる理由はい ます。困ったことに最近のウインドウもの ろいろ考えられますが , ひとつの単純な理 のプログラムは巨大な switch 文を簡単に作 ☆☆ ( 中程度 ) 深刻度状況によっては☆☆☆ ( 重度 ) 由ではなく , 複数の理由が絡み合い , 積み らせるような仕様になっています。 [ 症状 ] 重なって , できてくる場合がほとんどです。 たとえば , List 25 に示した Pspr ⅱⅲ関数は たまに , こういう巨大関数を作るプログラ プログラムの解読や改造が困難になりま 85 行あります。これを見て , 「まだこの程 度なら根性を出せば理解できる」という人 マのなかに「これだけの巨大関数を作れる す。また , バグが発生しても退治しにくく もいれば , 「 50 行を超えるのは勘弁してく のは技術的にすぐれているからだ」と自己 なります。 満足をしている場合もありますが , 見る人 [ 原因 ] れ」という人もいます。よく聞かれる目安 が見れば単なる無能である証拠だと見破っ 不勉強と怠慢が原因です。 として , 「 1 画面のなかで収まる程度の行数」 [ 対策 / 予防 ] とか , 「プリンタ用紙の 1 ページ以内に収ま てしまいます。一刻も早く認識を改めるべ まとまりのない , 読みにくいプログラム るのが限度」などがあります。 きでしよう。 ちなみに先ほどの List25 を複数の関数に を書くプログラマには「もっと勉強してく ところが筆者が実際に目にする例は , そ ださい」といいたいところですが , この手 分割して書き直すと , List 26 のようになり んな「かわいい」話ではなく , 2000 行とか ます。 List 25 に比べると , ある程度「構造」 の人たちは , 絶対といっていいほど勉強し 3000 行に及ぶ「最長不倒関数」とでもいいた てくれません。事実上 , 効果的な [ 対策 / 予 が見えやすくなっていることがわかるでし くなる凶悪な例です。ただし , それがめっ 防 ] はないのかもしれません ( 苦笑 ) 。 たに現れない特殊な例だと思うと大間違い このようにすることで , より よう。また List 25 を複数の関数に分割して書き直した例 void Psprintf(char *outBuf,const char *inFormat, ... ) va—list theVA; va-start(theVA,inFormat); 血 e ( * 土 nFo て t ) ( if(*inFormat = = s 響 ch ( * ( 土 0 て t + 1 ) ) { case C outBuf = P—Char—Buf(outBuf,va—arg(theVA,int)); inFormat 十 = 2 ー break; outBuf = P—IntN—Buf(outBuf,va—arg(theVA,int),10); 十 = inFormat break; P—Text—Buf(outBuf,va—arg(theVA,char 料 outBuf inFormat 十 = 2 を break; List 26 s 0 char *P—Char—Buf(char *i0Buf,int inC) 土 f ( 土 sp て土北 ( 土 nc ) ) ( * 土 OBuf 十十 (char)inc; )else{ 土 OBuf 十十 = ' ? ' return 土 oBu static char *P—Text—Buf(char *i0Buf,char *inT) whiIe(*inT){ 社 oBuf + 十 = * 土 nT 十 て e し u てれ ioBuf; static char *P—IntN—Buf(char *i0Buf,int i 心 , 加と inRadix) ch 矼 theBuft16J; char *thePtr; 土 f ( i 心く 0 ) ( * 土 oBuf 十十 = ' ー ' 土心 * = -1 theptr = P—IntN-Buf-Sub ( theBuf ,inNum , inRadix whiIe(--thePtr 〉 = theBuf ) ( * 土 OBuf 十十 *thePtr ー return ioBuf ー st 北土 0 char *P—IntN-Buf-Sub(char *ioBuf,int 土心Ⅷ , 土北 inRadix) static char theN[ ] = ” 0123456789ABCD を F ・ while(inNum > まånRadix) { *ioBuf 十十 = theN[inNum き inRadix]; ま心 / = 土れ Rad 土 *i0Buf 十十 = th は心島 return iOBuf ー case X P—IntN—Buf(outBuf,va—arg(theVA,int),16Yi outBuf inFormat 十 2 ・ break; case ー *outBuf 十十 = inFormat 十第 break; default: *outBuf 十十 = *inFormat 十 break; }else{ *outBuf 十十 = *inFormat 十 *outBuf va—end(theVA); 36 C MAGAZINE 2000 4