コーディングの方法とリファクタリングの原理

Chanyiに、リファクタリング原理の問題点を踏まえて、**コーディング**に使えるメソッドや**コーディング**の現状についての考えを語っていただきました。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/the-refactoring-principle-and-why-it-matters-to-anyone-coding_595514)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

#方法1：手書きでコードを書く
ほとんどの初心者[JAVA](https://www.java.com/en/?spm=a2c65.11461447.0.0.31d5474dAMXb9I)プログラマーは、開発ツール上で以下のコードを筋肉の記憶力だけでタイプアウトしてしまうことがあります。

```
public class Test {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}
```

そう、これは誰もが最初にコードを入力したときに覚える古典的な「Hello world」文です。

当然のことながら、手動でコードを入力することも選択肢の一つです。そして実際には、コードを手入力することは、プログラマーとしての能力をテストするのに適しています。実際、多くの企業では、面接の過程でコンピュータプログラミングの試験の一部として手動コーディングを使用しています。このような試験では、面接官は試験の要件に基づいてプログラミングツール（[Eclipse](https://www.eclipse.org/?spm=a2c65.11461447.0.0.31d5474dAMXb9I)など）を選択し、特定のコードを手書きで書いたり、デバッグしたり、実行したりする必要があります。コーディングプロセス全体の間、面接官はインターネットで答えを検索したり、オンラインのヘルプ文書を見たりすることはできません。一般的には、面接官が完全に自分でコードを書くことができることが要件とされています。このような試験では、構文、関数、論理、思考力、アルゴリズム、実地能力など、面接官のコーディング能力が試されます。

マニュアルコーディングは、優秀なプログラマーなら誰でも持っているべき、本当に持っていなければならない基本的なスキルです。マニュアルコーディングは、記事を書くために必要な基本的なライティングスキルのようなものです。構文とは、コードの中の文章を作るための方法です。関数とは、記事の中の単語や文章のことです。クラスライブラリとは、引用するための逸話、アーキテクチャとは、表現のジャンルです。機能は記事を書くための主な目的です。アルゴリズムは言語を整理するためのロジックです。したがって、プログラミング言語の構文をマスターし、基本的なクラスライブラリの機能の束を覚え、必要なサードパーティのクラスライブラリをいくつか引用し、成熟した安定したアーキテクチャを選択し、製品要求の機能を明確にし、ロジックを実現するアルゴリズムを選択する必要があります。そして、マニュアルコーディングは記事を書くのと同じくらい簡単です。

#方法2：コードをコピーして貼り付ける
中国の諺で "唐の詩300首をよく勉強すれば、書けなくても詠めるようになる。"とあるように、 コーディングも同じことが言えます。コーディングの第一歩は真似をすることです。つまり、コードをコピー＆ペーストすることです。コードのコピー＆ペーストは芸術です。この方法を正しく使えば、半分の労力でコーディングを完成させることができます。必要なコードが出てきたら、コピー＆ペーストする前に慎重にチェックしましょう。あるシナリオに適したコードが、必ずしも別のシナリオに適しているとは限りません。有資格者であるプログラマーは、コードを確認せずに単純にコードを取って使うことはしません。

###1. なぜコードをコピーして貼り付けるのか
1、既存のコードをコピー＆ペーストすることで、開発時間を短縮することができます。
2、安定したコードをコピー＆ペーストすることで、システム障害のリスクを減らすことができます。
3、ネットワークコードをコピー＆ペーストすることで、他の人の実績を自分の実績に変換することができます。

###2. コードのコピー＆ペーストで発生する問題点
1、コピーしたコードをどの程度理解していますか？実装ロジックは妥当ですか？コードは安定して動作しますか？潜在的なバグはどれくらいありますか？
2、プロジェクト内で何回コードをコピー＆ペーストしましたか？3回目に同じことをしたらリファクタリングの原則に基づいて、同じコードをリファクタリングする必要がありますか？
3、コードをコピー＆ペーストする回数が増えれば増えるほど、コードメンテナンスの問題が出てきます。複数のバージョンでコードを更新した後もコードの同期を保つためには、それぞれの場所で同じ変更をしなければなりません。そのため、メンテナンスコストやリスクが増大してしまいます。

要するに、コードのコピー＆ペーストは他のコーディング方法と同じで、どの方法が優れているということはありません。あくまでも、適切にも不適切にも使える方法です。コードをコピー＆ペーストする場合は、その結果に責任を持たなければなりません。

#方法3：テキスト置換によるコードの生成
###1. 生成されたコードの例
以下は、ユーザークエリを実装するために書かれたコードの一部です。

```
/** Query user service function */
public PageData<UserVO> queryUser(QueryUserParameterVO parameter) {
    Long totalCount = userDAO.countByParameter(parameter);
    List<UserVO> userList = null;
    if (Objects.nonNull(totalCount) && totalCount.compareTo(0L) > 0) {
        userList = userDAO.queryByParameter(parameter);
    }
    return new PageData<>(totalCount, userList);
}

/** Query user controller function */
@RequestMapping(path = "/queryUser", method = RequestMethod.POST)
public Result<PageData<UserVO>> queryUser(@Valid @RequestBody QueryUserParameterVO parameter) {
    PageData<UserVO> pageData = userService.queryUser(parameter);
    return Result.success(pageData);
}
```

企業クエリを実装するコードを書く場合、コード形式はユーザークエリのコードと似ています。置換関係は以下のように整理できます。

1、"User "を "Company "に置き換えてください。
2、"user "を "company "に置き換えてください。
[Notepad](https://www.rapidtables.com/tools/notepad.html?spm=a2c65.11461447.0.0.31d5474dAMXb9I)や[EditPlus](https://www.editplus.com/?spm=a2c65.11461447.0.0.31d5474dAMXb9I)などのテキストエディタを使って、共通テキストを大文字小文字を区別して置換することができます。最終的な結果は以下のようになります。

```
/** Query company service function */
public PageData<CompanyVO> queryCompany(QueryCompanyParameterVO parameter) {
    Long totalCount = companyDAO.countByParameter(parameter);
    List<CompanyVO> companyList = null;
    if (Objects.nonNull(totalCount) && totalCount.compareTo(0L) > 0) {
        companyList = companyDAO.queryByParameter(parameter);
    }
    return new PageData<>(totalCount, companyList);
}

/** Query company controller function */
@RequestMapping(path = "/queryCompany", method = RequestMethod.POST)
public Result<PageData<CompanyVO>> queryCompany(@Valid @RequestBody QueryCompanyParameterVO parameter) {
    PageData<CompanyVO> pageData = companyService.queryCompany(parameter);
    return Result.success(pageData);
}
```

テキスト置換でコードを生成する場合は、コード生成時間が1分を超えないようにしてください。

###2. メリットとデメリット
**メリット:**
1、コード生成速度が速い。
**デメリット:**
1、サンプルコードをコンパイルする必要があります。
2、この方法は、テキスト置換のシナリオにのみ適用されます。

#方法 4: Excel の数式を使用してコードを生成する
エクセルの数式は非常に強力で、いくつかの定式化されたコードをコンパイルするために使用することができます。

###1. Excel の数式を使用してモデルクラスを生成する
インターフェースモデルの定義をWikiからExcelにコピーします。サンプルデータは以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/e76e4a81-a3dd-58a9-a5f2-b50f9a350275.png)

以下のようにエクセルの計算式を書きます。

```
= "/** "&D6&IF(ISBLANK(F6), "", "("&F6&")")&" */ "&IF(E6 = "否", IF(C6 = "String", "@NotBlank", "@NotNull"), "")&" private "&C6&" "&B6&";"
```

次のように数式を使ってコードを生成します。

```
/** User ID */ @NotNull private Long id;
/** Username */ @NotBlank private String name;
/** User gender (0:unknown;1:male;2:female) */ @NotNull private Integer sex;
/** User description */  private String description;
```

モデルクラスを作成し、以下のようにコードを整理します。

```
/** User DO Class */
public class UserDO {
    /** User ID */
    @NotNull
    private Long id;
    /** User Name */
    @NotBlank
    private String name;
    /** User gender (0:unknown;1:male;2:female) */
    @NotNull
    private Integer sex;
    /** User description */
    private String description;
    ......
}
```

###2. Excelの式を使用して列挙クラスを生成する
Wikiから列挙定義をExcelにコピーします。サンプルデータは以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/b688655d-4098-d9de-9e6f-607f9e53deb2.png)


以下のようにエクセルの計算式を書きます。

```
="/** "&D2&"("&B2&") */"&C2&"("&B2&", """&D2&"""),"
```

次のように数式を使ってコードを生成します。

```
/** empty(0) */NONE(0, "Unknown"),
/** male(1) */MAN(1, "Male"),
/** female(2) */WOMAN(2, "Female"),
```

列挙クラスを作成し、以下のようにコードを整理します。

```
/** User gender enumeration class */
public enum UserSex {
    /** enumeration definition */
    /** empty(0) */
    NONE(0, "unknown"),
    /** male(1) */
    MAN(1, "male"),
    /** female(2) */
    WOMAN(2, "female");
    ......
}

```

###3. Excelの数式を使ってデータベース・ステートメントを生成する
会社リストは、エクセルでは以下のようにソートされています。このリストに基づいて、レコードを直接データベースに挿入するためのSQL文を書く必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/4072cb0a-41e9-cc1d-b22a-705cb926d343.png)

以下のようにエクセルの計算式を書きます。

```
= "('"&B2&"', '"&C2&"', '"&D2&"', '"&E2&"'),"
```

式を使用して、以下のようなSQL文を生成します。

```
('AutoNavi', 'First Tower', '(010)11111111', 'gaode@xxx.com'),
('Alibaba Cloud', 'Green village', '(010)22222222', 'aliyun@xxx.com'),
('Cainiao', 'Alibaba offices', '(010)33333333', 'cainiao@xxx.com'),
```

into文ヘッダを追加し、SQL文を以下のように並べ替えます。

```
insert into t_company(name, address, phone, email) values
('AutoNavi', 'First Tower', '(010)11111111', 'gaode@xxx.com'),
('Alibaba Cloud', 'Green village', '(010)22222222', 'aliyun@xxx.com'),
('Cainiao', 'Alibaba offices', '(010)33333333', 'cainiao@xxx.com');
```

###4. メリットとデメリット
**メリット:**
1、この方法は、テーブルベースのデータのコード生成に適用できます。
2、式を書いた後、ドラッグ＆ドロップでコードを生成することができます。そのため、コード生成速度が速いです。
**デメリット:**
1、この方法は複雑な関数を使ったコードの生成には適用できません。
#方法5：ツールを使ってコードを生成する
つまり、既存のツールを使ってコードを生成します。多くの開発ツールはコードを生成するツールを提供しています。例えば、コンストラクタの生成、基底クラスやインターフェイス関数のリロード、ゲッター/セッター関数の生成、toString関数の生成などです。これらのツールを使用することで、手動でコーディングする手間を省くことができます。また、いくつかのコード生成プラグインを使用して、特定のアプリケーション・シナリオを満たすコードを生成することもできます。

以下では、MyBatis-Generatorプラグインを例に、コード生成ツールの使い方を説明します。


###1. プラグインのインストールと実行
詳しくは、インターネットで関連資料を検索してください。

###2. 生成されたコードの例
**2.1. モデルクラスのコードを生成する**
User.javaファイルの内容は以下の通りです。

```
......
public class User {
    private Long id;
    private String user;
    private String password;
    private Integer age;
    ......
}
```

**2.2 マッパーインターフェースコードの生成**
UserMapper.javaファイルの内容は以下の通りです。

```
......
public interface UserMapper {
    User selectByPrimaryKey(Long id);
    ......
}
```

**2.3. マッパー XML コードの生成**
UserMapper.xmlファイルの内容は以下の通りです。

```
......
<mapper namespace="com.test.dao.UserMapper" >
  <resultMap id="BaseResultMap" type="com.test.pojo.User" >
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="user" property="user" jdbcType="VARCHAR" />
    <result column="password" property="password" jdbcType="VARCHAR" />
    <result column="age" property="age" jdbcType="INTEGER" />
  </resultMap>
  <sql id="Base_Column_List" >
    id, user, password, age
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long" >
    select 
    <include refid="Base_Column_List" />
    from test_user
    where id = #{id,jdbcType=BIGINT}
  </select>
  ......
</mapper>
```

###3. メリットとデメリット
**メリット:**
1、コード生成プラグインを使用してコードを生成します。そのため、コード生成速度が速いこと。
2、プラグインの設定ファイルを使用して、所望の機能コードの生成を制御します。
**デメリット:**
1、コード生成プラグインの使用方法を勉強して慣れるまでに時間がかかること。
2、生成されたコードが必ずしもコード仕様を満たしているとは限らない。生成のたびにコードの適合性チェックが必要になります。
3、コードを再生成した後は、カスタムコードが上書きされる可能性が高い為、別のコードライブラリを管理し、DIFFツールを使用してコードの違いを識別し、値を割り当ててから、異なるコードを貼り付けることをお勧めします。

#方法6：コードを使ってコードを生成する
つまり、自分で書いて、自分流にコードを生成します。以下では、MyBatisベースのデータベースアクセスのコードを例にとり、この方法を説明します。

###1. テーブル情報の問い合わせ
まず、コード生成に必要なテーブルとカラムの情報をデータベースから取得します。

**1.1. テーブル情報を問い合わせる**
テーブル情報を問い合わせるためのステートメントは以下の通りです。

```
select t.table_name as 'table name'
, t.table_comment as 'table remarks'
from information_schema.tables t
where t.table_schema = ?
and t.table_type = 'BASE TABLE'
and t.table_name = ?;
```

1つ目のクエスチョンマークはデータベース名に割り当てられた値を、2つ目のクエスチョンマークはテーブル名に割り当てられた値を示します。

テーブル情報のクエリ結果は以下のようになります。

|SN.|	Table name	|Table remarks|
|:-----------|------------:|:------------:|
|1  |	org_company	|Organization company table|

**1.2. クエリカラム情報**
カラム情報をクエリするためのステートメントは以下の通りです。

```
select c.column_name as 'column name'
, c.column_comment as 'column remarks'
, c.data_type as 'data type'
, c.character_maximum_length as 'character length'
, c.numeric_precision as 'numeric precision'
, c.numeric_scale as 'numeric scale'
, c.column_default as ''
, c.is_nullable as 'optional?'
, c.column_key as 'column key name'
from information_schema.columns c
where c.table_schema = ?
and c.table_name = ?
order by c.ordinal_position;

```

1つ目のクエスチョンマークはデータベース名に割り当てられた値を、2つ目のクエスチョンマークはテーブル名に割り当てられた値を示しています。

**カラム情報のクエリ結果は以下のようになります。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/9374c71e-b58e-a930-37db-493f2dbc2898.png)


###2 コードのコンパイルと生成
**2.1 モデル・クラス・コードのコンパイルと生成**
```
/** Generate model class file function */
private void generateModelClassFile(File dir, Table table, List<Column> columnList) throws Exception {
    try (PrintWriter writer = new PrintWriter(new File(dir, className + "DO.java"))) {
        String className = getClassName(table.getTableName());
        String classComments = getClassComment(table.getTableComment());
        writer.println("package " + groupName + "." + systemName + ".database;");
        ......
        writer.println("/** " + classComments + "DO class */");
        writer.println("@Getter");
        writer.println("@Setter");
        writer.println("@ToString");
        writer.println("public class " + className + "DO {");
        for (Column column : columnList) {
            String fieldType = getFieldType(column);
            String fieldName = getFieldName(column.getColumnName());
            String fieldComment = getFieldComment(column);
            writer.println("\t/** " + fieldComment + " */");
            writer.println("\tprivate " + fieldType + " " + fieldName + ";");
        }
        writer.println("}");
    }
}
```

**2.2 DAOインターフェースコードのコンパイルと生成**

```
/** Generate DAO interface file function */
private void generateDaoInterfaceFile(File dir, Table table, List<Column> columnList, List<Column> pkColumnList) throws Exception {
    try (PrintWriter writer = new PrintWriter(new File(dir, className + "DAO.java"))) {
        String className = getClassName(table.getTableName());
        String classComments = getClassComment(table.getTableComment());
        writer.println("package " + groupName + "." + systemName + ".database;");
        ......
        writer.println("/** " + classComments + "DAO interface */");
        writer.println("public interface " + className + "DAO {");
        writer.println("\t/** get" + classComments + "function */");
        writer.print("\tpublic " + className + "DO get(");
        boolean isFirst = true;
        for (Column pkColumn : pkColumnList) {
            if (!isFirst) {
                writer.print(", ");
            } else {
                isFirst = false;
            }
            String fieldType = getFieldType(pkColumn);
            String fieldName = getFieldName(pkColumn.getColumnName());
            writer.print("@Param(\"" + fieldName + "\") " + fieldType + " " + fieldName);
        }
        writer.println(");");
        ......
        writer.println("}");
    }
}

```

**2.3 DAO マッパーコードのコンパイルと生成**

```
/** Generate DAO mapping file function */
private void generateDaoMapperFile(File dir, Table table, List<Column> columnList, List<Column> pkColumnList) throws Exception {
    try (PrintWriter writer = new PrintWriter(new File(dir, className + "DAO.xml"))) {
        String className = getClassName(table.getTableName());
        String classComments = getClassComment(table.getTableComment());
        writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        ......
        writer.println("<!-- " + classComments + "Mapping -->");
        writer.println("<mapper namespace=\"" + groupName + "." + systemName + ".database." + className + "DAO\">");
        writer.println("\t<!—All field statements -->");
        writer.println("\t<sql id=\"fields\">");
        if (CollectionUtils.isNotEmpty(columnList)) {
            boolean isFirst = true;
            String columnName = getColumnName(pkColumn.getColumnName());
            for (Column column : columnList) {
                if (isFirst) {
                    isFirst = false;
                    writer.println("\t\t" + columnName);
                } else {
                    writer.println("\t\t, " + columnName);
                }
            }
        }
        writer.println("\t</sql>");
        writer.println("\t<!-- get" + classComments + "function statement -->");
        writer.println("\t<select id=\"get\" resultType=\"" + groupName + "." + systemName + ".database." + className + "DO\">");
        writer.println("\t\tselect");
        writer.println("\t\t<include refid=\"fields\"/>");
        writer.println("\t\tfrom " + table.getTableName());
        boolean isFirst = true;
        for (Column pkColumn : pkColumnList) {
            String columnName = getColumnName(pkColumn.getColumnName());
            String fieldName = getFieldName(pkColumn.getColumnName());
            writer.print("\t\t");
            if (isFirst) {
                writer.print("where");
                isFirst = false;
            } else {
                writer.print("and");
            }
            writer.println(" " + columnName + " = #{" + fieldName + "}");
        }
        writer.println("\t</select>");
        writer.println("</mapper>");
    }
}
```

###3 関連するコードの生成
**3.1 生成されたモデルクラスのコード**

```
/** Organize company DO class */
@Getter
@Setter
@ToString
public class OrgCompanyDO {
    /** company logo */
    private Long id;
    /** company name */
    private String name;
    /** contact address */
    private String address;
    /** company description */
    private String description;
}
```

**3.2 生成されたDAOインタフェースコード**

```
/** Organize company DAO interface */
public interface OrgCompanyDAO {
    /** Get organization company function */
    public OrgCompanyDO get(@Param("id") Long id);
}
```

**3.3 生成されたDAOマッパーコード**

```
<!—Organize company mapping -->
<mapper namespace="xxx.database.OrgCompanyDAO">
    <!—All field statement -->
    <sql id="fields">
        id
        , name
        , address
        , description
    </sql>
    <!—Get organization company function statement -->
    <select id="get" resultType="xxx.database.OrgCompanyDO">
        select
        <include refid="fields"/>
        from org_company
        where id = #{id}
    </select>
</mapper>
```

###4. メリットとデメリット
**メリット：**
1、コードフォーマットをカスタマイズして、生成されたコードのコンプライアンスを確保することができます。
2、コード機能をカスタマイズして、目的のコードだけを生成することができます。
3、事前のコード沈殿の後、コードは後で直接使用することができます。
**デメリット：**
1、コード生成に必要なデータを確実に取得するためにデータソースの検討が必要です。
2、データモデルの作成やコンパイル、コード生成に時間がかかります。

#究極のメソッド：メソッドだけに固執しない
必要なものを直接コンピュータに伝えれば、コンピュータが自動的にコードを生成してくれるという究極のコーディング方法でしょうか。これは将来的に技術がある程度発展してから現実のものになるかもしれません。しかし、現在ではこの方法は非現実的です。現実には、上司やプロダクトマネージャー、テクニカルマネージャーでもない限り、「口を開けばすぐにコードを生成する」ということはできません。

コーディングの究極の方法は、方法だけに固執するのではなく、適切な方法であれば何でも使うことです。この記事で挙げたすべてのコーディング方法は、それぞれにメリットやデメリットがあり、様々なシナリオに適用可能です。そのため、様々なコーディング方法を柔軟に使い分けることが、本当の究極のコーディング方法と言えるでしょう。

#コードの標準化
先行するコーディング方法の多くは、サンプルコードを手作業でコンパイルする必要があります。コードがコード仕様に準拠していない場合、コード間の共通点を見つけ出し、標準として機能するサンプルコードを抽象化することが困難になります。標準として機能するサンプルコードがコード仕様に準拠していなければ、生成されたコードもコード仕様に準拠しておらず、不準拠は数十倍、数百倍、数千倍に拡大してしまいます。そのため、コードの標準化はコーディングの最優先事項になります。


*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*