重複コード改善 いろいろな手法

重複コードを避けたい

2017年8月、画像の膨張・収縮アルゴリズムについて調べたときに、双方のコードの重複が気になりました。
コピペしますか?
重複コードを避ける簡単なコード改善から究極的なコード改善まで整理します。

画像処理 膨張・収縮アルゴリズム

膨張処理も収縮処理も、Y座標とX座標によるラスタスキャン・ループの部分は同じコードで重複しています。実際、片方のコードはコピペして作成してしまいました。

簡単なコードなので、重複していることは気になりませんし、このコードのままで良いと考えています。しかし、もう少し複雑なコードだった場合はどうでしょうか?いつまでも、コピペを続けますか?

機能コードによる実装

メイン・ループの処理が重複していますので、ループを一本化してみます。

こんなとき、C言語の一般的プログラマーが考える方法は機能コードによる実装です。

機能コード ImageFunc (Dilation:膨張、Erosion:収縮)を定義して、ラスタスキャン・ループの関数に渡します。
ループの中では、機能コードを判定して膨張処理か収縮処理を呼びます。


C++言語によるアルゴリズムコードを示します。

 

enum ImageFunc {
   Dilation,    // 膨張
   Erosion      // 収縮
};
//
void Image_process( ImageFunc efunc ,int iw ,int ih ,const unsigned char *pucSrc ,unsigned char *pucDst )
{
    for(int iy=1; iy<ih-1; iy++)
    {
        for(int ix=1; ix<iw-1; ix++)
        {
            unsigned char uc = pucSrc[ (iy+0)*iw + (ix+0) ];    // 中央
            unsigned char ua = pucSrc[ (iy-1)*iw + (ix+0) ];    // 上
            unsigned char ub = pucSrc[ (iy+1)*iw + (ix+0) ];    // 下
            unsigned char ul = pucSrc[ (iy+0)*iw + (ix-1) ];    // 左
            unsigned char ur = pucSrc[ (iy+0)*iw + (ix+1) ];    // 右
            //
            unsigned char ux;
            if      (efunc == Dilation)      { ux = Func_dilation(uc ,ua ,ub ,ul ,ur); }
            else if (efunc == Erosion)       { ux = Func_erosion (uc ,ua ,ub ,ul ,ur); }
            else                             { ux = uc;                                }
            //
            pucDst[ iy*iw + ix ] = ux;
        }
    }
}
unsigned char Func_dilation(unsigned char uc ,unsigned char ua ,unsigned char ub ,unsigned char ul ,unsigned char ur)
{
    unsigned char ux =  0x00;
    if (uc != 0x00)   { ux = 0xFF; }
    if (ua != 0x00)   { ux = 0xFF; }
    if (ub != 0x00)   { ux = 0xFF; }
    if (ul != 0x00)   { ux = 0xFF; }
    if (ur != 0x00)   { ux = 0xFF; }
    //
    return ux;
}

 

unsigned char Func_erosion(unsigned char uc ,unsigned char ua ,unsigned char ub ,unsigned char ul ,unsigned char ur)
{
    unsigned char ux =  0xFF;
    if (uc != 0xFF)   { ux = 0x00; }
    if (ua != 0xFF)   { ux = 0x00; }
    if (ub != 0xFF)   { ux = 0x00; }
    if (ul != 0xFF)   { ux = 0x00; }
    if (ur != 0xFF)   { ux = 0x00; }
    //
    return ux;
}
  // 画像膨張
  Image_loop_func( Dilation ,iw ,ih ,pucSrc ,pucDst  );
  // 画像収縮
  Image_loop_func( Erosion  ,iw ,ih ,pucSrc ,pucDst  );

機能コードによる実装について

直感的でとてもシンプルなソースコードになりますが、Y座標とX座標の二重ループの中で1画素ごとに機能コードの判定と画像膨張/画像収縮の関数呼び出しが発生します。ループのオーバーヘッドが気になるところです。

コード改善 いろいろ

重複コードを改善する方法にもいろいろな手法があります。

  • 機能コードによる実装 : C言語の一般的プログラマー
  • 関数ポインタによる実装 : C言語の熟練プログラマー
  • ポリモーフィズムによる実装 : オブジェクト指向好きなC++言語の実践的プログラマー
  • ジェネリックプログラミングによる実装 : 探究心の強いC++言語の名人プログラマー

重複コード改善 関数ポインタによる実装


重複コード改善 ポリモーフィズムによる実装


重複コード改善 ジェネリックプログラミングによる実装


どの手法を採用するかは、ソースコードの保守性実行効率のどちらが優先事項かにもよります。画像膨張・収縮のような簡単なコードの場合、無理して重複コードを除去する必要はないと考えます。もとのソースコードが一番シンプルで、実行効率も良いです。

しかし、メイン・ループの処理がもっと複雑だった場合は、少し考えてしまいます。特に、プロセッサに最適化実装して処理効率を最大限に向上させる場合は、私ならジェネリックプログラミングによる実装を採用します。

画像膨張処理と画像収縮処理のメインルーチンをコピペして両方のメインループを最新状態に保守し続ける自信がないのですね。