プログラマとして就職してから早数年・・・
そこで習得した知識を今一度思い起こすためのページです。まぁ、備忘録です。
C++がメイン。でも、たまに他の言語に浮気します。
自宅の開発環境をVisual C# 2005 から 2008にしました。
これで、前から興味を持っていたラムダ式について勉強できます。
試しに簡単なプログラムを組んでみました。
class Program
{
static void Main( string[] args )
{
// この配列から5を見つけるには?
int[] arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 1)真っ先に思いつく書き方
foreach( int n in arr )
{
if( n == 5 )
Console.WriteLine( "5が見つかりました。" );
}
// 2)ラムダ式を使った書き方
if( arr.Any( x => x == 5 ) )
{
Console.WriteLine( "5が見つかりました。" );
}
}
}
1)は、for文でぐるぐる回して配列の要素が条件に合致するか検索しているだけです。
2)は、ラムダ式を使って1)とほぼ同じ処理を実現してます。
※Any(Func(T, Boolean))は、配列の中に条件に当てはまる任意の要素があるか判断するメソッドです。
この手の処理は結構使いますから、ラムダ式で簡潔にできれば、全体としてかなりスッキリすると思います。 便利な書き方なので、これから積極的に使っていきたいものです。
ラムダ式を使用できる環境であれば、積極的に使っていく。
2008/12/07 作成
プログラミングにおいて、条件分岐は必要不可欠です。
C++であればこう書きます。
…
if(条件式)
{
// 条件式がtrue時の処理
…
}
else
{
// 条件式がfalse時の処理
…
}
…
条件式は、式の結果が真(true)または偽(false)になるものを記述します。
0が偽として、0以外が真として扱われます。
例として、0以上10以下の数字はAと出力し、それ以外ならBと出力するプログラムを作ります。
int main(void)
{
for( int i = 0; i < 20; i++ )
{
if( ( 0 <= i ) && ( i <= 10 ) )
{
printf( "A" );
}
else
{
printf( "B" );
}
}
return 0;
}
大体、こんな感じです。
次に、5だけはBとして出力したいという要望があったとして、プログラムを変更します。
int main(void)
{
for( int i = 0; i < 20; i++ )
{
if( ( 0 <= i ) && ( i <= 10 ) && (i != 5 ) )
{
printf( "A" );
}
else
{
printf( "B" );
}
}
return 0;
}
できましたが、条件式が複雑になってきました。
また、今回と同じような要望が出る可能性があります。
そこで条件式を関数化しましょう。
// Aと表示する値か?
static bool IsDispAValue( int value )
{
if( ( 0 <= i ) && ( i <= 10 ) )
{
if( (i != 5 ) )
{
return true;
}
else
{
return false;
}
}
return false;
}
int main(void)
{
for( int i = 0; i < 20; i++ )
{
if( IsDispAValue( i ) )
{
printf( "A" );
}
else
{
printf( "B" );
}
}
return 0;
}
条件式を関数化することによって、条件が抽出され読みやすくなります。
また、関数化によって条件式に名前を付けられます。
条件式が複雑になるようなら読みやすいように関数化してすっきりさせる。
2008/11/01 作成
ほとんどのプログラミング言語では変数・関数名は自由に付けることができます。
日本語で付けることはできないしてもアルファベットなら大丈夫です。
void funt01()
{
…
}
void func02()
{
…
}
こんな感じに適当に名づけることも可能です。
では、この関数になんらかの処理を組み込んで呼び出せるようにしましょう。
// func01.h
void funt01();
// func01.cpp
void funt01()
{
int a = 3;
int b = 5;
printf( "%d\n", a + b );
}
// func02.h
void funt02();
// func02.cpp
void func02()
{
int a = 3;
int b = 5;
printf( "%d\n", a - b );
}
// main.cpp
#include "func01.h"
#include "func02.h"
int main( void )
{
func01();
func02();
return 0;
}
組み込んでみました。
しかし、main.cppを見ただけでは何をしてるのかさっぱり分かりません。
では、どうすれば分かりやすくなるでしょうか?
func01(), func02()の名前が無意味だからだと思います。
なので名前に意味を持たせるように書き換えてみました。
// SanTasuGoWoHyouji.h
void SanTasuGoWoHyouji();
// SanTasuGoWoHyouji.cpp
void SanTasuGoWoHyouji()
{
int a = 3;
int b = 5;
printf( "%d\n", a + b );
}
// SanHikuGoWoHyouji.h
void SanHikuGoWoHyouji();
// SanHikuGoWoHyouji.cpp
void SanHikuGoWoHyouji()
{
int a = 3;
int b = 5;
printf( "%d\n", a - b );
}
// main.cpp
#include "SanTasuGoWoHyouji.h"
#include "SanHikuGoWoHyouji.h"
int main( void )
{
SanTasuGoWoHyouji();
SanHikuGoWoHyouji();
return 0;
}
ローマ字表記なのでかっこ悪く見えますが、
func01(), func02()よりは分かりやすくなったと思います。
関数の処理の内容に沿った関数名が付けられているだけで
プログラムはかなり読みやすくなります。
これは、実際の開発現場でかなり重要なことです。
なぜなら、プログラムを書くことよりも読むことのほうが多いからです。
これによって、いざ修正となったときの労力が大きく違います。
変数・関数名を考えるときは変数・関数の目的を考える。
2008/09/14 作成
前回、他人のコードを読むことが多いと書きました。
同様に他人のコードを修正することも多いです。
人それぞれコーディングに癖があり、誰一人として同じ書き方をする人はいません。
なんとなく自分のスタイルに合わない書き方もあります。
※ほとんどの場合、合わないと感じるのは些細な違いです。
例えば、インデントの付け方やスペースの箇所などです。
if( i == 0 ){
・・・
}
else if( i == 1 ){
・・・
}
else{
・・・
}
↑
これぐらいの違いです。
↓
if(i==0){
・・・
}else if(i==1){
・・・
}else{
・・・
}
書き方に拘り時間をかけすぎるとプログラムの修正に使える時間が減ります。
使える時間が減るということは、余裕がなくなります。
余裕がなくなると気持ちが焦り、バグが発生しやすくなります。
また、気に入らない記述だからといって変更してしまうと
その変更が原因でバグが発生することもあります。
正常に動作している個所に手を加えるほど無駄なことはありません。
書き方に囚われると余裕がなくなる。
気に入らないから安易に変更すれば、
時間を浪費するだけでなく、バグ発生のリスクも高まる。
2008/07/21 作成
多くのプログラマは、新規でコードを書くより、
他人の書いたコードを修正をするほうが多いです。
なぜなら、作るのは1回ですが、
その後数年に渡って保守やバージョンアップをしなければならないからです。
コードに修正を加えるためには、内部処理について理解する必要があります。
適切な修正箇所を探るためにコードを読みます。頭の中で処理を追っかけるわけです。
しかし、コードの量が膨大なので修正に必要ないところは読みたくないです。
ただでさえ他人のコードは読みにくいというのに・・・
(ちなみに他人から見れば私のコードも読みにくいと思います。)
そこで頼りになるのが、コード内に書いてあるコメントです。
↓こんなのです。
// この行はコメントです。
/*
この間はコメントです。
*/
しかし、困ったことにコメントは処理の中身と違うことがあります。
それは納期が迫ると処理の変更に手一杯でコメントまでに手が回らないからです。
一度、間違ったままになるとそのコメントはほぼ放置されます。
(誰も好んで他人が書いたコメントを修正しようとは思いません。)
// この関数は、a+bの足し算を行います。
static int Add( int a, int b )
{
return a + b;
}
↑このコードを
↓このように修正したとします。しかし、コメントは修正しませんでした。
↓(本来ならコメントも修正すべきです。)
// この関数は、a+bの足し算を行います。
static int Add( int a, int b )
{
return a + b + 1;
}
さて、コメントを信じて以下の処理を作成しました。
修正前後で処理はどう変化するでしょうか?
static int AddLoop( int limit )
{
int ret = 0;
for( int i = 1; i <= limit; i++ ){
// ここで足し算を行う。
ret = Add( ret, i );
}
return ret;
}
int main( void )
{
printf( "%d\n", AddLoop( 10 ) );
return 0;
}
修正前は55, 修正後は65になります。
1〜10の足し算の結果が返ってくると思ったら何か違う値が返ってきます。
コメントを鵜呑みにすると、期待通りに動かず、どこが悪いか探さなければなりません。
一度コメントを信用している以上、その処理が間違っていると考えません。
『原因は他の場所にあるはず』と考え、見当違いのところを探します。
結果、バグを発見するまでに時間がかかってしまいます。
最初に関数の戻り値をチェックしておけば、探す必要もなかったのです。
コメントには注意する。
コメントが絶対的に正しいということはない。
一回はコメント通りに動作しているか処理をデバッグする。
2008/07/05 作成
- ソースA -
static void Calc1( int a, int b )
{
int add = a + b;
int sub = a - b;
int multi = a * b;
int div = a / b;
printf( "%d, %d, %d, %d\n", add, sub, multi, div );
}
↑このソースを
↓このようにします。
- ソースB -
static int Add( int a, int b )
{
return a + b;
}
static int Sub( int a, int b )
{
return a - b;
}
static int Multi( int a, int b )
{
return a * b;
}
static int Div( int a, int b )
{
return a / b;
}
static void Calc2( int a, int b )
{
int add = Add( a, b );
int sub = Sub( a, b );
int multi = Multi( a, b );
int div = Div( a, b );
printf( "%d, %d, %d, %d\n", add, sub, multi, div );
}
ソースAに比べてソースBは長くなりました。
お分かりのように、どちらのソースも0で除算する可能性があります。
それを簡単に直したのが次のソースです。
- ソースA -
static void Calc1( int a, int b )
{
int add = a + b;
int sub = a - b;
int multi = a * b;
int div = ( b != 0 ) ? a / b : 0;
printf( "%d, %d, %d, %d\n", add, sub, multi, div );
}
- ソースB -
static int Div( int a, int b )
{
return ( b != 0 ) ? a / b : 0;
}
今回は割り算を行っている箇所が1つしかありませんでした。
なので、修正は容易にできました。
しかし、ソースAのように割り算処理を分割していない場合、その箇所が何百となるとすべてを修正しなければなりません。
対して、ソースBの書き方ではDiv()の中身を修正するだけでよいのです。
ソースAに比べてソースBは簡単に修正できます。
処理は可能な限り処理ごとに分割する。
結果として、処理の中身が一行になっても。
最初はちょっと面倒でも処理を分割しておきましょう。
2008/06/22 作成