この記事は、
計算機の基本原理を味わおう21 - オブジェクトの役割をきっちり決めて結合度を下げようの続きです。前回は、オブジェクトの結合度に関する事柄を解説しました。今回は、リファクタリングのゴールについて解説します。
どこまで、リファクタリングすればよいのかという質問をよく受けるので、今回はリファクタリングのゴールについて書きます。最終的なリファクタリングのゴールはありません。何故ならば、そのシステムが使われ続け、ヴァージョンアップし続ける限り、リファクタリングの必要性があるからです。しかしながら、一時的なゴールがないと大変心細いです。リファクタリングは職人的な作業であり、熟練者は感覚的に理解していますが、慣れていない人はわからないようです。それはよくない状態ですので目安を書きます。
リファクタリングの目安は、
オブジェクトを参照するコードが明瞭簡潔である状態になるまでです。「明瞭簡潔」もまた、感覚的な問題をはらんでいますが、コードは目に見えるので、感覚よりもましだと思います。
具体例として、この連載で作成している最小計算機のサンプルコードをもとに考えてみましょう。
using System;
using System.Diagnostics;
using MiniBitMachine;
using MiniBitMachine.V3;
using MiniBitMachine.V3.Utility;
class Sample
{
//命令をテストする。
private static ExecutionResult Execute(
IfCode code,
Cpu cpu )
{
cpu.Reset();
code.SaveData();
Console.WriteLine( code.Name + "命令を実行します・・・" );
Console.WriteLine( "---------- 実行前 ---------- " );
Console.WriteLine( code.ToString( "v" ) );
ExecutionResult result = cpu.AllRun();
Console.WriteLine( "---------- 実行後 ---------- " );
Console.WriteLine( code.ToString( "v" ) );
Console.WriteLine( "---------- 状態 ---------- " );
Console.WriteLine( result );
Debug.Assert( !code.CheckData() );
return result;
}
//テストプログラムの開始部分
private static void TestMain(
Cpu cpu,
IfCode code )
{
code.SetBinary();
Console.WriteLine(
"********** " + code.Name + "命令の内容 **********" );
Console.WriteLine( Cpu.GetEnvironment() );
Console.Write( cpu );
Console.Write( code.ToString( "a" ) );
Console.WriteLine( );
Console.Write( code.ToString( "d" ) );
Console.WriteLine( );
ExecutionResult result;
result = Execute( code, cpu );
result += Execute( code, cpu );
Console.WriteLine( "★★★★★ 総計 ★★★★★" );
Console.WriteLine( result );
}
//inc命令のテストを行う。
private static void IncTest( Memory m )
{
byte ds = 1;
byte cs = 2;
Cpu cpu = new Cpu( ds, cs, m );
byte carryFlagAddress = 4;
byte targetAddress = 3;
Inc inc = new Inc(
targetAddress,
carryFlagAddress,
cpu );
TestMain( cpu, inc );
Console.WriteLine();
}
//not命令のテストを行う。
private static void NotTest( Memory m )
{
byte ds = 3;
byte cs = 4;
Cpu cpu = new Cpu( ds, cs, m );
byte targetAddress = 3;
Not not = new Not(
targetAddress,
cpu );
TestMain( cpu, not );
Console.WriteLine();
}
static void Main()
{
Memory m = new Memory();
IncTest( m );
NotTest( m );
Console.ReadLine();
}
}
大体これぐらい参照する側のコードが減ればいいと思います。このコードを実装するために、オブジェクトの階層化をし、書式文字を受け付けるToStringメソッドを用意しました。
リファクタリングでオブジェクトの継承を使用するのは、ちょっと難易度が高いし、本来はオブジェクト指向設計で決めておくべきなのですが、全てが設計段階でわかりません。柔軟にオブジェクト指向プログラミング → オブジェクト指向設計 → オブジェクト指向プログラミングというふうに、行動を切り替えましょう。そもそも、
オブジェクト指向は、ウォーターフォールモデルで開発するものではなく、改善を積み重ねて行うものですから、これがオブジェクト指向本来の姿です。
サンプルコードにあるIfCodeオブジェクトは、NotとIncオブジェクトの親です。両オブジェクトがif構造に基づいて構築されており、ほかにもif構造のオブジェクトが考えられるので作成しました。IfCodeオブジェクトは次のようにしています。
using System;
using System.Collections.Generic;
using System.Linq;
namespace MiniBitMachine.V3
{
//If文により構築されている命令を表すオブジェクト。
public abstract class IfCode : CompositeInstruction
{
//inc命令を構成するcmov群。
protected If _if;
//インスタンスを生成する。
public IfCode( string name ) : base( name ) { }
//命令を生成する。
protected void CreateInstruction(
Cpu cpu,
bool addLast)
{
CompareMove[] compareCodes = GetCompareCodes();
CompareMove[] trueCodes = GetTrueCodes();
CompareMove[] falseCodes = GetFalseCodes();
this._if = new If(
cpu,
compareCodes,
trueCodes,
falseCodes,
addLast);
this.Compares = this._if.Compares;
}
//比較フラグ設定に関するコードを取得する。
protected abstract CompareMove[] GetCompareCodes();
//比較対象が真の場合に実行するコードを取得する。
protected abstract CompareMove[] GetTrueCodes();
//比較対象が偽の場合に実行するコードを取得する。
protected abstract CompareMove[] GetFalseCodes();
//インスタンスの文字列表現を取得する。
public override string ToString()
{
return this._if.ToString();
}
//インスタンスの文字列表現を取得する。
public string ToString( string value )
{
switch ( value ) {
case "v":
return GetValueString();
case "d":
return this._if.ToString();
case "a":
return GetAddressString();
default:
throw new FormatException(
"書式" + value + "には対応してません。" );
}
}
//アドレスを表すインスタンスを取得する。
protected abstract string GetAddressString( );
//値を表すインスタンスを取得する。
protected abstract string GetValueString( );
//オブジェクトが持つ各種値を保存する。
internal abstract void SaveData( );
//オブジェクトが保持している値と、現在値を比べ、
//同じ値ならばtrueを返す。
internal abstract bool CheckData( );
//自身のバイナリ値をメモリにセットする。
public void SetBinary()
{
this._if.SetBinary();
}
}
}
参考のためにIncオブジェクトも掲載します。
using System;
using System.Collections.Generic;
using System.Text;
namespace MiniBitMachine.V3
{
//inc(インクリメント)命令
public sealed class Inc : IfCode
{
//命令を実行するCPU。
private Cpu _cpu;
//実行環境に関する情報。
private ExecuteEnvironment _info;
//対象のアドレス。
private byte _targetAddress;
public byte TargetAddress
{
get { return this._targetAddress; }
}
//対象の値
public bool TargetValue
{
get { return this._cpu.ReferenceMemory.Read(
this._cpu.Addresser.GetDataAddress(
this._targetAddress ) ); }
}
//保存している値。
private bool _saveValue;
//桁上がりを表わすフラグの場所。
private byte _carryFlagAddress;
public byte CarryFlagAddress
{
get { return this._carryFlagAddress; }
}
//桁上がりを表わすフラグの値。
public bool CarryFlagValue
{
get
{
ushort address =
this._cpu.Addresser.GetDataAddress(
this._carryFlagAddress );
return this._cpu.ReferenceMemory.Read( address );
}
}
//保存しているキャリーフラグの値。
private bool _saveCarryFlag;
//対象のアドレス、桁上がりフラグのアドレス、
//CPUの情報、を元に、インスタンスを生成する。
public Inc(
byte targetAddress,
byte carryFlagAddress,
Cpu cpu)
: this( targetAddress, carryFlagAddress, cpu, true )
{
}
//対象のアドレス、桁上がりフラグのアドレス、
//CPUの情報、を元に、インスタンスを生成する。
public Inc(
byte targetAddress,
byte carryFlagAddress,
Cpu cpu,
bool addLast ) : base( "inc" )
{
this._targetAddress = targetAddress;
this._carryFlagAddress = carryFlagAddress;
this._cpu = cpu;
this._info = Cpu.GetEnvironment();
this.CreateInstruction(cpu, addLast );
}
//比較フラグ設定に関するコードを取得する。
protected override CompareMove[ ] GetCompareCodes( )
{
CompareMove[ ] result = new CompareMove[ 1 ];
result[ 0 ] = new CompareMove(
this._info.CompareFlagAddress,
this._targetAddress,
"実行フラグの場所に対象データを移動" );
return result;
}
//比較対象が真の場合に実行するコードを取得する。
protected override CompareMove[ ] GetTrueCodes()
{
CompareMove[] result = new CompareMove[ 2 ];
result[ 0 ] = new CompareMove(
this._targetAddress,
this._info.OneAddress,
"対象を1に設定" );
result[ 1 ] = new CompareMove(
this._carryFlagAddress,
this._info.ZeroAddress,
"桁上がりフラグを0に設定" );
return result;
}
//比較対象が偽の場合に実行するコードを取得する。
protected override CompareMove[ ] GetFalseCodes()
{
CompareMove[ ] result = new CompareMove[ 2 ];
result[ 0 ] = new CompareMove(
this._targetAddress,
this._info.ZeroAddress,
"対象を0に設定" );
result[ 1 ] = new CompareMove(
this._carryFlagAddress,
this._info.OneAddress,
"桁上がりフラグを1に設定" );
return result;
}
//アドレスを表すインスタンスを取得する。
protected override string GetAddressString( )
{
System.Text.StringBuilder result = new System.Text.StringBuilder();
result.Append( "対象アドレス:" );
result.Append( this.TargetAddress );
result.Append( Environment.NewLine );
result.Append( "桁上がりフラグのアドレス:" );
result.Append( this.CarryFlagAddress );
result.Append( Environment.NewLine );
return result.ToString();
}
//値を表すインスタンスを取得する。
protected override string GetValueString( )
{
System.Text.StringBuilder result = new System.Text.StringBuilder();
result.Append( "対象値:" );
result.Append( this.TargetValue );
result.Append( Environment.NewLine );
result.Append( "桁上がりフラグ:" );
result.Append( this.CarryFlagValue );
result.Append( Environment.NewLine );
return result.ToString();
}
//オブジェクトが持つ各種値を保存する。
internal override void SaveData( )
{
this._saveValue = this.TargetValue;
this._saveCarryFlag = this.CarryFlagValue;
}
//オブジェクトが保持している値と、現在値を比べ、
//同じ値ならばtrueを返す。
internal override bool CheckData( )
{
if ( this._saveValue == this.TargetValue ) return true;
if ( this._saveCarryFlag == this.CarryFlagValue &
this._saveValue ) return true;
return false;
}
}
}
なお、CompositeInstructionオブジェクトは、IfとIfCodeの親であり、命令を表すInstructionオブジェクトの子です。両オブジェクトは現在のところ、タグ的な意味合いが強いですが、機能が追加されるにつれて、大きな力を発揮すると思います。
今回はこれで終わりです。Hello worldプログラムよりも量が多いプログラミングをするとき、リファクタリングは避けられません。特にこういった学習のためのプログラミングでは、リファクタリングの腕が学習効果に結び付くと思います。リファクタリング力をアップさせて、プログラミング力もアップさせましょう。続く...
テーマ : プログラミング
ジャンル : コンピュータ