くそコードが無くなる日を目指して

実例はC++/Javaがメインです|良いコードは健康を増進します

Category: コード実例あり

クラスを継承したときの初期化(コンストラクタ等)と対になる終了処理(デストラクタ等)の話。

初期化の順番ってどれほど気にしているでしょうか?例として、
public class InitializePattern1 extends Parent {
    public InitializePattern1() {
        initializePattern1Class();
        super();
    }
}


public class InitializePattern1 extends Parent {
    public InitializePattern1() {
        super();
        initializePattern1Class();
    }
}

だったら、どっちが良いと思いますか?どっちでも良いと思いますか? 
前者はコンパイルエラーになります(コンストラクタなので、たぶん)。

なぜか。

親を継承した子クラスは親クラスが存在して初めて成立するので、
親クラスが準備出来ていない時に子クラスが何かする(この場合は初期化する)と、
親クラスの初期化されていないメンバにアクセスしたりして、予期しない動作を起こす可能性がでてきてしまうからです。
→だから順番が強制されているのです。(終了処理は必然的に順番が逆になります)
ちなみに親が子のことを予め知ることはできないので、親→子の順番で初期化するのは何も問題おきません・・・。
(こういう理由でprivateなコンストラクタを持つクラスは継承できないのです。つまり、コンストラクタにつく「private」は継承を禁止にするキーワードでは全くなく、コンストラクタがprivateだと呼べないから結果的に継承できなくなってるだけなのです)

この例はコンストラクタなので、当たり前なところがありますが、最近流行り?のAndroid界隈では結構こんなコードを見かけます。
public class SampleActivity extends Activity {
    @Override
    public onCreate() {
        doInitialization(); // 先に初期化しちゃってる
        super.onCreate();
    }

    public onDestroy() {
        super.onDestroy(); 
        doClosing();// 後で終了処理しちゃってる
    }
}
これは、コンストラクタやデストラクタ(C++)以外は、特に順番関係なく呼べちゃうからできちゃうんですね。
でも上で言った話があるので、本当は逆にしたほうがいいです。

最後に関連して一つ追加すると、
初期化と終了は対になっているはずなので、A->B->Cの順番で初期化したら
C->B->Aの順番で終了されているのが正しいです。
(もちろん互いに関係なければ順番は関係ないですが、お作法的な配置の話です)
public class SampleActivity extends Activity {
    @Override
    public onCreate() {
        super.onCreate();
        doInitializationA(); // Aを初期化
        doInitializationB(); // Bを初期化
        doInitializationC(); // Cを初期化
    }

    public onDestroy() {
        doClosingC(); // Cを終了
        doClosingB(); // Bを終了
        doClosingA(); // Aを終了
        super.onDestroy();
    }
}

まとめると、初期化は親→子、終了処理は子→親の順番でやるのが正しくて、順番が逆なのはなにも気にしていないか、よほどの理由があるときしかありえない。

ぜひ気をつけましょう。

 

名前付けは重要。そんなことはわかってるという人はたくさんいると思います。
でも、中身/内容が同じだったら全部同じ名前でいいと思っていたりしないでしょうか?

細かいことを気にしなければ、確かに中身が同じコード=同じメソッド名、変数名でもいいかもしれません。でもこのブログは 、くそコードが無くなる日を目指している手前、そんな中途半端なことを、やってOK!とは口が裂けても言いません。

具体的にどういうことか、ちょっと例を示しながら説明したいと思います。

TVが付いているという状態を表現したいとします。
特に何も考えないのであれば、
 
public class XXX {
    ...
    boolean mIsTvOn = true;
}
のようにするわけです。良さそうに見えます。
でもこれで問題ないかどうかはXXX次第です。
XXXがLivingRoomだったとします。
 
public class LivingRoom {
    ...
    boolean mIsTvOn = true;
}
リビングのテレビがついている。至って自然です。
ではXXXがTv(テレビ自身)だったらどうでしょう?
 
public class Tv {
    ...
    boolean mIsTvOn = true; // Tv自身がついている?それとも別のTvがついている?
}
少し混乱します。「TVがついている」という状態は、文章でみると無意識的に想像して、表現が同じになりそうですが、実際は誰から見るかで、表現のされ方が変わらなければいけないとわかります。
つまりTvから見れば、自身がついているときは、自分自身がTvなことは自明なので、 
public class Tv {
    ...
    boolean mIsTurnedOn = true; // Tv自身がついている
}
とすべきといえます。

一言でまとめると、「視点の違いで適切な名前は変わる」ということです。
こういった名前付けはメソッドでも同じです。

是非気をつけてみてください。 

コードを書いているときは目的の機能を早く完成させたくなるがゆえに、細かいとこに気がいきにくくなりがちですが、それは危険です。
よく言われることですが、最終的にそうして出来上がったコードはそのときだけ 動く、汎用性の低いものになっていて、次の機能を追加するときに問題(=バグ)を引き起こします。
もちろん無視してそのようなコードを書き続けてもいいですが、そのサイクルが続けば続くほど汎用性は下がり、どこかの時点で破綻します。

でも、決して汚くしようとしてしているわけではないのに、なぜ問題が大きくなるまで放置してしまうのでしょうか?
これは破綻が突然起きるのではなく、すごく小さなほころびの積み重ねが原因となっているからです。

ちょっと簡単な例を見ながら、どういったところがほころびなのか見てみましょう。

まず、次のような消費税込みの値段を計算するメソッドがあったとします。

class TaxCalculator {
    ...
    public int calculateTaxIncludedPrice(int price) {
        return price * 1.05;
    }
    ...
}
税率を5%としてみました。この時点ですでにほころびがありますが、未来永劫コードに変更がなければおそらく問題は起きません。
しかし、2014年から税率が8%になることがわかり、2014年以降の計算を変更しなければいけなくなったとします。
ここで、問題が発生します。なぜか、上のコードを見た人はきっと、
あー要するに2014年から1.05が1.08になるだけだよね。つまりこうでしょ!
class TaxCalculator {
    ...
    public int calculateTaxIncludedPrice(int price) {
        Calendar date = Calendar.getInstance();
        if (2014 <= date.get(Calendar.YEAR)) {
            return price * 1.08;
        }
        return price * 1.05;
    }
    ...
}
と、考える可能性が高いです。これでだいぶ問題が大きくなりました。
でも、最初の、ほころびが小さいので、これで何が、どうだめになったのかよくわからないかもしれません。

上のコードの問題点は、直接的には1.05、1.08という数字がハードコードされていることなのですが、根本的にはそもそも消費税込みの計算をするということがどういうことかきちんと考えられてない点にあります。
つまり、税込みの値段というのは本当は、
物の値段×税率
であるべきなのに、
物の値段×なんらかの数字
という表現がコード上されてしまっているのが問題です。

物の値段×税率という考え方が最初からあれば、上のコードたちはこうなっていたはずです。
class TaxCalculator {
    private static final float TAX_RATE = 1.05f;
    ...
    public int calculateTaxIncludedPrice(int price) {
        return price * TAX_RATE;
    }
    ...
}
class TaxCalculator {
    private static final float TAX_RATE;
    static {
        Calendar date = Calendar.getInstance();
        if (2014 <= date.get(Calendar.YEAR)) {
            TAX_RATE = 1.08;
        } else {
            TAX_RATE = 1.05;
        }
    }
    ...
    public int calculateTaxIncludedPrice(int price) { 
        return price * TAX_RATE;
    }
    ...
} 
もし、別になにも変化がないと思うようでしたら、世界中の様々な国の税率に対して計算をすることを考えてみてください。
前者では、計算式そのものの部分に変更が加わります、でも後者は国別に税率を設定するだけで済みます。
計算式の部分に変更がはいると、そもそも税率が変わるだけなのに、計算式をいじるという意味的なおかしさが生じる上、なんかのきっかけで掛け算が足し算に変わったり、各国ごとの計算があっているのかといった不要な不安を覚えることにあります。

コードを書くときは、そもそもこの計算は何なのか、何が目的なのかそういう意識をもって書くことを心がけてみてください。

こんばんは。
今日は短めに。こんなのはやめましょうという話。
 
    try {
        exceptionThrower();
    } catch (Exception e) {
        // Catch everything
    }

ExceptionがexceptionThrower(注:これはこの例のために作ったメソッドです)から投げられて、なんかtry〜catchを書かないとコンパイルが通らないという理由で上のように書くのはやめましょう。 めんどくさいからとりあえず、Exceptionでキャッチしとけ!はやめましょう。 最初想定したExceptionだけキャッチするようにしましょう。 全部キャッチしてしまうと、想定外のバグが埋もれたり、変なところに影響がでて迷宮入りしやすくなります。

コード=読みやすくあるべきという観点で見ると、書く際に気をつけるべきは命名と分割という話。

命名を意識するというのは次のような感じです。
例えばこんなコードがあったとします。
String orange = "Apple"; // これは論外
String info = "Some Information";
前者はそもそも書いてあることと中身が違うので、混乱をきたすどころか、バグでも起きた日には迷宮入りしかねません。後者は、結局なんの情報?という部分が欠落しているので、読み手は中身が正しいのかどうか判定できません。(前後の文脈で判断を仰ぐのは、時間の無駄になるので適切ではありません)
一方で、命名がしっかりしていれば、細かなロジックがわからなくても何をしているのか理解できる(ここが重要)はずなので、命名はコードを書く際に意識すべき要素だといえます。

分割を意識するというのは次のような感じです。
次のコードを見てください
void eatBreakfast() {
    while () {
        〜
    }
    while () {
        〜
    }
}
eatBreakfastの中で2つの処理が混在しています。これでもいいのでは?という意見はあると思いますが、これでは、ぱっと見た時に、eatBreakfast(朝ごはんを食べる)が2つのwhileループからなることはわかりますが、それが一体何をしているのかはわかりません。
(じっくり読めばわかるんだと思いますが、それをさせるのは良いコードではありません)

なぜわかりにくいのかというと、朝ごはんを食べる処理にはいくつか分割できる要素がある(eatBreakfastは抽象度が高い)のですが、それがそのまま(抽象度が高いまま)になっているからです。
そこでeatBreakfastが、実際には、「パンを食べる」と「コーヒーを飲む」から構成されていたとするならば、
void eatBreakfast() {
    eatBread();
    drinkCoffee();
}
となります。明らかにわかりやすくなりました。

命名と分割 、ぜひ意識してコードを書いて見てください。

それでは、良いコード日和を 

↑このページのトップヘ