nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

switch文を爆散

はじめに

コードのあちこちに同じようなswitch文処理が書かれている場合があります。

機能追加・修正するたびに変更が必要なswitch文処理を探してコード修正しなくてはなりません。

このようなswitch文処理を見つけたら、ポリモーフィズムが使うことで、リファクタリングできないか考えてみましょう。

また、他のクラスのインスタンスを取得してswitch文で条件分岐処理している場合は、リファクタリングの優先度が高いと考えています。

書籍

  • 新装版 リファクタリング 既存のコードを安全に改善する
    • Martin Fowler [著]
    • P83

static Factory Method Patternを用いて、switch文を削除

switch文を削除する方法はポリモーフィズムを使用しますが、ポリモーフィズムの使用方法はいくつかあります。

そのときの状況で最適な方法が変化すると思います。

本記事では、(1)の方法を使用して、コード例のswitch文を削除します。

  1. static Factory Method Patternで削除
  2. Factory Method Patternで削除
  3. Stateパターンで削除
  4. Starategyパターンで削除

コード例

コード例は、従業員の職業ID値と職業名を表示する処理です。

Mainクラスで、Employeeクラスのインスタンスを生成します。

そして、MainクラスのshowId関数とshowTypeName関数で、Employeeクラスのインスタンスの情報を元に、switch文で条件分岐処理をします。

本コードを(1)の方法で、リファクタリングしていきます。

言語はJavaを使用してますが、オブジェクト指向プログラミングが実現できる言語であれば、どんな言語でも実現可能です。

修正前のコード

本コードで問題なのは、従業員の職業IDの種類が増えるたびに、MainクラスのshowId関数とshowTypeName関数の両方をコード修正しなくてはなりません。

Mainクラスが仮に巨大で複雑なクラスであった場合は、コード修正洩れが発生しやすいです・・・。

クラス図

f:id:nprogram:20190618071624p:plain

[Main.java]

import java.util.*;

public class Main {
    
    public static final int ENGINEER = 0;
    public static final int SLAESMAN = 1;
    public static final int MANAGER = 2;
    
    public static void main(String[] args) throws Exception
    {
        // (1) インスタンスを生成
        // (2) 関数に作成したインスタンスを渡す
        
        Employee engineer = new Employee(ENGINEER);
        showTypeName(engineer);
        showId(engineer);
        
        Employee salesman = new Employee(SLAESMAN);
        showTypeName(salesman);
        showId(salesman);
        
        Employee manager = new Employee(MANAGER);
        showTypeName(manager);
        showId(manager);
        
        Employee nullEmployee = new Employee(-9999); // 不正値を入力する
        showTypeName(nullEmployee);
        showId(nullEmployee);
    }
    
    public static void showId(Employee employ)
    {
        switch(employ._typeId)
        {
            case ENGINEER:
                System.out.println(employ._typeId);
                break;
            case SLAESMAN:
                System.out.println(employ._typeId);
                break;
            case MANAGER:
                System.out.println(employ._typeId);
                break;
            default:
                System.out.println(employ._typeId);
                break;
        }
    }
    
    public static void showTypeName(Employee employ)
    {
        switch(employ._typeId)
        {
            case ENGINEER:
                System.out.println("ENGINEER");
                break;
            case SLAESMAN:
                System.out.println("SLAESMAN");
                break;
            case MANAGER:
                System.out.println("MANAGER");
                break;
            default:
                System.out.println("Error!! Illegal Parameter.");
                break;
        }
    }
}   

[Employee.java]

public class Employee
{
    public int _typeId;

    
    public Employee(int type)
    {
        _typeId = type;
    }
}
ENGINEER
0
SLAESMAN
1
MANAGER
2
Error!! Illegal Parameter.
-9999

修正後のコード

static Factory Methodパターンを用いて、MainクラスのshowId関数とshowTypeName関数のswitch文を削除します。

手順

おおまかな手順は以下のとおりです。

  1. サブクラス(本例では、Enginner, Salesman, Managerクラス)を作成します。
  2. 共通部分はスーパークラス(本例では、Empolyeeクラス)で定義します

  3. Creatorクラス(本例では、CreatorEmployeeクラス)を作成します。

    • Creatorクラスは、インスタンス化しません(インスタンス化を防ぎます)
    • factoryMethodメソッドで、サブクラスのインスタンスを生成します
  4. Mainクラスのswitch文を削除します

クラス図

f:id:nprogram:20190619230651p:plain

[Main.java]

import java.util.*;

public class Main
{
    public static void main(String[] args) throws Exception
    {
        // (1) インスタンスを生成
        // (2) 関数に作成したインスタンスを渡す
        
        Employee engineer = createEmployee(CreatorEmployee.ENGINEER);
        showTypeName(engineer);
        showId(engineer);
        
        Employee salesman = createEmployee(CreatorEmployee.SALESMAN);
        showTypeName(salesman);
        showId(salesman);
        
        Employee manager = createEmployee(CreatorEmployee.MANAGER);
        showTypeName(manager);
        showId(manager);
        
        Employee nullEmployee = createEmployee(-9999); // 不正値を入力する
        showTypeName(nullEmployee);
        showId(nullEmployee);
    }
    
    public static Employee createEmployee(int type)
    {
        return CreatorEmployee.factoryMethod(type);
    }
    
    
    public static void showId(Employee employ)
    {
        // switch文を撲滅完了。ただし、NullPointerExceptionの例外を防ぐため、Nullチェックのif文は必要。
        if (employ != null)
        {
            System.out.println(employ.getTypeName());  
        }
    }
    
    public static void showTypeName(Employee employ)
    {
        // switch文を撲滅完了。ただし、NullPointerExceptionの例外を防ぐため、Nullチェックのif文は必要。
        if (employ != null)
        {
            System.out.println(employ.getTypeId());  
        }
    }
}

[CreatorEmployee.java]

// 本クラスは継承を禁止するため、final修飾子を付与します
public final class CreatorEmployee
{
    // 本例では、定数の定義はCreatorEmployeeクラスが持ちます
    public static final int ENGINEER = 0;
    public static final int SALESMAN = 1;
    public static final int MANAGER = 2;
    public static final int ILLEGAL_EMPLOYEE = -1;
    
    // コンストラクタをprivateにすることで、インスタンス化を禁止させましょう
    private CreatorEmployee(){ }
    
    // factoryMethodメソッドの役割は与えられたパラメータによって生成するインスタンスを切り替えること
    public static Employee factoryMethod(int type)
    {
        switch(type)
        {
            case ENGINEER:
                return new Engineer(ENGINEER);
                
            case SALESMAN:
                return new Salesman(SALESMAN);
                
            case MANAGER:
                return new Manager(MANAGER);
                
            default:
                System.out.println("Error!! Illegal Parameter.");
                return null;
        }
    }
}

[Employee.java]

// 抽象クラス
public abstract class Employee
{
    protected int _typeId;
    protected String _typeName;
    
    public abstract int getTypeId();
    
    public abstract String getTypeName();
}

[Engineer.java]

// 具象クラス
public class Engineer extends Employee
{
    public Engineer(int typeId)
    {
        _typeId = typeId;
    }
    
    @Override
    public int getTypeId()
    {
        return _typeId;
    }
    
    @Override
    public String getTypeName()
    {
        return "Engineer";
    }
}

[Salesman.java]

// 具象クラス
public class Salesman extends Employee
{
    public Salesman(int typeId)
    {
        _typeId = typeId;
    }
    
    @Override
    public int getTypeId()
    {
        return _typeId;
    }
    
    @Override
    public String getTypeName()
    {
        return "Salesman";
    }
}

[Manager.java]

public class Manager extends Employee
{
    public Manager(int typeId)
    {
        _typeId = typeId;
    }
    
    @Override
    public int getTypeId()
    {
        return _typeId;
    }
    
    @Override
    public String getTypeName()
    {
        return "Manager";
    }
}
0
Engineer
1
Salesman
2
Manager
Error!! Illegal Parameter.

まだ、switch文残っているじゃん・・・。

はい。まだswitch文残っています。CreatorEmployeeクラスのfactoryMethodメソッドは、引数に異常値を渡した場合、戻り値としてNullのインスタンスを返します。

そのため、factoryMethodメソッド関数の呼び出し元では、Nullチェックが必要なのです。(Nullチェックしないと、JavaではNull Pointer Exceptionのエラーが発生)

    public static void showId(Employee employ)
    {
        // switch文を撲滅完了。ただし、NullPointerExceptionの例外を防ぐため、Nullチェックのif文は必要。
        if (employ != null)
        {
            System.out.println(employ.getTypeName());  
        }
    }

でも、ご安心ください。そのための秘策として、NullObjectパターンがあります。

以下の記事に、NullObjectパターンを用いて、Nullチェックを削除する方法について記載しました

リンク

Factory MethodパターンとAbstract Factory Methodパターンの違いについては、以下のサイトが詳しいです。

巨大クラスをクラス抽出で爆散

はじめに

巨大クラスを爆散したいときがあります。

その場合は、クラス抽出が便利です。

書籍

  • 新装版 リファクタリング 既存のコードを安全に改善する
    • Martin Fowler [著]

手順

(1) クラスの責務を切り出す方法を決める

(2) 切り出された責務を記述する新たなクラスを作る

(3) 元の暮らすから新たなクラスにリンクを貼る

(4) 移動しようとする各フィールドに「フィールドの移動(p146)」を適用する

(5) 移動が終わるごとにコンパイルしてテストする

(6) メソッドの移動(p142)を適用して元の暮らすから新たなクラスにメソッドを移動する

(7) 移動が終わるごとにコンパイルしてテストする

(8) 各クラスのインタフェースを見直して、減らす

(9) 新たなクラスを公開するか、公開するならそれを参照オブジェクトとして公開するのか、不変な値オブジェクトとして公開するのか決める

Personクラスの中に、電話番号のインスタンスがあります。

クラス分割を行い、Personクラスが持つ電話番号関連の責務を別クラスに持たせましょう。

分割前

import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Person person = new Person("Tom", "123-", "123-4567");
        
        System.out.println(person.getName());
        System.out.println(person.getTelepthonNumber());
        System.out.println(person.getOfficeNumber());
    }
}
public class Person
{
    private String _name;
    private String _officeAreaCode;
    private String _officeNumber;
    
    public Person(String name, String AreaCode, String number)
    {
        _name = name;
        _officeAreaCode = AreaCode;
        _officeNumber = number;
    }
    
    public String getName()
    {
        return _name;
    }
    
    public String getTelepthonNumber() 
    {
        return _officeAreaCode + _officeNumber;
    }
    
    void setOfficeAreaCode(String arg)
    {
        _officeAreaCode = arg;
    }
    
    String getOfficeNumber()
    {
        return _officeNumber;
    }
    
    void setOfficeNumber(String arg)
    {
        _officeNumber = arg;
    }
}

分割後

import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception
    {
        Person person = new Person("Tom", "123-", "123-4567");
        
        System.out.println(person.getName());
        System.out.println(person.getTelepthonNumber());
        System.out.println(person.getOfficeNumber());
    }
}
public class Person
{
    private String _name;

    private final Telephone telephone;
    
    public Person(String name, String areaCode, String number)
    {
        _name = name;
        telephone = new Telephone(areaCode, number);
    }
    
    public String getName()
    {
        return _name;
    }
    
    public String getTelepthonNumber() 
    {
        return telephone.getTelepthonNumber();
    }
    
    public String getOfficeNumber() 
    {
        return telephone.getOfficeNumber();
    }
    
    public String getAreaCode() 
    {
        return telephone.getAreaCode();
    }
}
public class Person
{
    private String _name;

    private final Telephone telephone;
    
    public Person(String name, String areaCode, String number)
    {
        _name = name;
        telephone = new Telephone(areaCode, number);
    }
    
    public String getName()
    {
        return _name;
    }
    
    public String getTelepthonNumber() 
    {
        return telephone.getTelepthonNumber();
    }
    
    public String getOfficeNumber() 
    {
        return telephone.getOfficeNumber();
    }
    
    public String getAreaCode() 
    {
        return telephone.getAreaCode();
    }
}

リンク

以下のサイトは、巨大クラスを爆散するのにとても役立ちます。

リファクタリング入門

はじめに

以下の書籍を読んでリファクタリングを学習します。

書籍

  • 新装版 リファクタリング 既存のコードを安全に改善する
    • Martin Fowler [著]

リファクタリングの定義

外部から見たときのふるまいを保ちつつ、理解や修正が簡単になるようにソフトウェアの内部構造を変化させること

変更しにくいプログラムとは

  • 読みにくいプログラムは変更しにくい
  • ロジックが重複しているプログラムは変更しにくい
  • 機能追加に伴い、既存のコード修正が必要となるプログラムは変更しにくい
  • 複雑な条件分岐の多いプログラムは変更しにいくい

リファクタリングのコツ

  • 小さな単位で行うこと

    • 失敗する(バグを埋め込む)可能性を軽減できる
  • 新規追加の作業とリファクタリングの作業を同じ作業単位(コミット)で行ってはならない。必ず分けること

    • バグ発生時、バグ発生個所の特定が難しい (新規追加部分が原因か、リファクタリング部分が原因か・・・)
    • コード変更差分がとても見づらくなる (コード差分が新規追加の差分なのか、リファクタリングの差分なのかすぐに判別不可能)

リファクタリングトピック集

巨大なクラス

1つのクラスがあまりにも多くの仕事をしている場合は、たいていインスタンス変数を持ちすぎています。
インスタンス変数が多すぎると、重複コードが存在する可能性が高くなります

巨大クラスをクラス抽出で爆散 - nprogram’s blog

長すぎるパラメータリスト

  • メソッドによるパラメータの置き換え(P.292)
    • 既知のオブジェクトに問い合わせることでパラメータのデータの1つを取得できる
  • オブジェクトそのものの受け渡し (P.288)
    • データをばらばらに渡さずにオブジェクトそのものを渡すようにできます
  • 論理的なオブジェクトとしてデータの集まりを表現できない場合
    • パラメータオブジェクトの導入 (P.295)

switch文に気をつけろ

他のオブジェクトの属性を調べるswitch文処理を書くことは常に間違いである。

switch文処理は、他のオブジェクトについてではなく、自分自身のオブジェクトの情報について行うべきである。

switch文を爆散 - nprogram’s blog

データの群れ

数個のデータがグループとなって、クラスのフィールドやメソッドのシグニチャなど、さまざまな箇所に現れることがあります。

こうした群れをなしたデータはオブジェクトとして、1つにまとめるべき。

属性や操作のパラメータの数を絞り込むことはコードの不吉なにおいをなくすのに役立ちます。

特性の横恋慕

処理の際に、他のオブジェクトのgetメソッドを何度も何度も呼び出している間違ったメソッドがよく見受けられる

処理、および処理に必要なデータを1つにまとめる

あとがき

本の格言は素晴らしいと思います。

  • コンパイラが理解できるコードは誰にでも書ける。優れたプログラマは、人間にとってわかりやすいコードを書く
  • 新規追加の帽子とリファクタリングの帽子を同時にかぶるな

JavaScript学習用リンク記事

はじめに

JavaScript学習用の記事です。

2019年5月時点 JavaScript初心者が参考にしていいモダンJavaScript解説サイト2つ - Qiita

リンク

  • js-primer
    • これからJavaScriptに入門したい人が、ECMAScript 2015以降をベースにして一からJavaScriptを学べる本です。
    • https://jsprimer.net/

UnicodeからUTF-8に変換する方法 [C++] [MFC]

はじめに

UnicodeからUTF-8に変換する方法を調べたので、記録します。

Convert Unicode (utf-16) CStringW to utf-8 CStringA

CStringA ConvertUnicodeToUTF8(const CStringW& uni)
{
    if (uni.IsEmpty()) return ""; // nothing to do
    CStringA utf8;
    int cc=0;
    // get length (cc) of the new multibyte string excluding the \0 terminator first
    if ((cc = WideCharToMultiByte(CP_UTF8, 0, uni, -1, NULL, 0, 0, 0) - 1) > 0)
    { 
        // convert
        char *buf = utf8.GetBuffer(cc);
        if (buf) WideCharToMultiByte(CP_UTF8, 0, uni, -1, buf, cc, 0, 0);
        utf8.ReleaseBuffer();
    }
    return utf8;
}

Convert utf-8 CStringA to Unicode (utf-16) CStringW

CStringW ConvertUTF8ToUnicode(const CStringA& utf8)
{
    if (utf8.IsEmpty()) return L""; // nothing to do
    CStringW uni;
    int cc=0;
    // get length (cc) of the new widechar excluding the \0 terminator first
    if ((cc = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0) - 1) > 0)
    { 
        // convert
        wchar_t *buf = uni.GetBuffer(cc);
        if (buf) MultiByteToWideChar(CP_UTF8, 0, utf8, -1, buf, cc);
        uni.ReleaseBuffer();
    }
    return uni;
}

参考リンク

Convert Unicode (utf-16) CString to utf-8 and reverse

仮説検証学習

はじめに

仮説検証を学習していきたいと思います。

(1) どうなりたいのか(狙う結果)

(2) 何によって確かめるのか(指標)?

(3) そのために何をするのか?

(4) なぜ狙う効果が得られるのか?

ポイント

  • 仮説検証の内容は、具体的な数値にしましょう
  • 各項目で、内容が一致しない事態が発生しないようにしましょう

TensorFlow Object Detection APIをWindowsで使ってみた

はじめに

以下のサイトを見てTensorFlow Object Detection APIをWindowsで使ってみようと思います。

TensorFlow Object Detection APIは、TensorFlowを利用して、画像に写っている物体を検出するためのフレームワークです。

インストール手順・実行方法は、Samurai Blog様のブログが素晴らしい記事を書いていますので、そちらを参考にするといいと思います。

(ただし、注意点にも記載しているTensorFlowのバージョンには注意)

https://www.sejuku.net/blog/56695

注意点

TensorFlowのバージョンはv1.12以上である必要があります。かなり新しいTensorFlowバージョンでなければ動かないため、注意が必要です。(2019年4月28日時点)

古いTensorflow Versionでは、以下のように正しく動きません。

ImportError                               Traceback (most recent call last)
<ipython-input-1-34f5cdda911a> in <module>
     18 
     19 if StrictVersion(tf.__version__) < StrictVersion('1.12.0'):
---> 20   raise ImportError('Please upgrade your TensorFlow installation to v1.12.*.')

ImportError: Please upgrade your TensorFlow installation to v1.12.*.

本記事では

以下の記事を見ながら作業を進めていきます。

TensorFlow Object Detection APIをWindowsで使ってみた - Qiita

Protobufのコンパイルができない問題については、以下のサイトに解決策が書かれています。

python - protoc object_detection/protos/*.proto: No such file or directory - Stack Overflow

for /f %i in ('dir /b object_detection\protos\*.proto') do protoc object_detection\protos\%i --python_out=.
(tensorflow_gpu) C:\Users\uv2ut\Jupyter_Project_GPU\TensorFlow_Object_Detection_API\models-master\research>python object_detection/builders/model_builder_test.py
......C:\Users\uv2ut\Anaconda3\envs\tensorflow_gpu\lib\site-packages\numpy\lib\type_check.py:546: DeprecationWarning: np.asscalar(a) is deprecated since NumPy v1.16, use a.item() instead
  'a.item() instead', DeprecationWarning, stacklevel=1)
.C:\Users\uv2ut\Anaconda3\envs\tensorflow_gpu\lib\site-packages\tensorflow\python\util\tf_inspect.py:55: DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec()
  if d.decorator_argspec is not None), _inspect.getargspec(target))
.........
----------------------------------------------------------------------
Ran 16 tests in 0.092s

OK

早速使ってみる

サンプルノートブックファイルは以下にあります。

(任意のフォルダー)\research\object_detection\object_detection_tutorial.ipynb

新たな写真を追加して判定させたい場合

上記のサンプルノートブックファイルが参照する画像ファイルは以下のフォルダーにあります。 (任意のフォルダー)\research\object_detection\test_images

私の環境下では、以下のファイルにサンプルコードの画像ファイルがありました。

C:\Users\uv2ut\obj-detect\models-master\research\object_detection\test_images

f:id:nprogram:20190428173501p:plain

参考リンク