Karakuri.com

ベンチャー企業で働くソフトウェアエンジニアの技術録

レガシーコードの特徴を具体的な4つの例を示して問題点をまとめます

新卒で入社した企業では20年物のソースコードが現役で動いていたりしました。しかも協力会社に丸投げした部分やインドにオフショアした部分などが入り混じってカオスとなっていました。エンジニアも玉石混在で、カオスに練度の低いエンジニアが保守拡張したりしてカオスがさらにカオスに。そんなソースコードには発狂しそうになるレガシーコード(一部ではウンコードと呼ぶ人もいる)が豊富に眠っていました。今回はレガシーコードの反面教師としてその具体例を挙げてみます。

レガシーコードの具体例

意図が分からない

if ( path != null )
{
    str = Path.Combine(path, fileName);
}
else
{
    str = path + fileName;
}

たぶんpathがnullでPath.Combineでクラッシュする不具合があったんでしょうね。それを修正した結果が上のコードになったのでしょう。pathがnullのときクラッシュすることはこれで回避できた(できてるこれ?)かもしれませんが、nullのときstrは本当に問題ないんですかね…。この不具合修正で新たな不具合を作っていそうです。

続きを読む

SwiftのisEmptyやCountはnilを返す可能性があることを忘れてしまう

C#でstring.Emptyを多様しているせいか、SwiftのisEmptyがnilとなることを想定せずにクラッシュさせてしまったことが昔ありました。そのときPlaygroundで挙動を確認したことがあったので、その結果を載せておきます。

nilの可能性を忘れてしまう変数

isEmpty

f:id:hazakurakeita:20180413222215p:plain
case5において、初期化していない非オプショナル型の配列は、オプショナル型か非オプショナル型かを明示するとisEmptyを呼び出すことができます。ビルドエラーにはなりません。このcase5をオプショナル型としてisEmptyを実行するとnilが返ってきます。更に、このcase5を非オプショナル型にアンラップすると何も返ってきません。例外が発生します。また、case6のように初期化していないオプショナル型の配列も、明示することでisEmptyを呼び出し可能です。こちらの場合はcase6をオプショナル型、非オプショナル型どちらでisEmptyを実行しても例外が発生します。

Count

CountもC#ではnullのイメージがないため、Swiftでは一瞬戸惑ってしまいます。

C#でもvarを使うべき3つの理由|Microsoftも使用を推奨

前職でコーディング規約を整備しましょうという話になり、配られたコーディング規約にC#の型指定でvarを使うという内容がありました。新卒の研修の際にはvarはネガティブな説明だったので驚いたのですが、採用した理由はMicrosoftのコーディング規約を参考にしたからとのこと。今ではvarをメインに使っています。

C#のvarとは

暗黙の型指定

ローカル変数を定義するときは明示的な型指定を行う必要がありません。コンパイラがコードから型を推測してビルドしてくれるので、型に関わらずvarで良いわけです。

var integer = 1;
var str = string.Empty;
var byteArray = new byte[] { 0x00, 0x01 };
var intList = new List<int>();
続きを読む

TryParseを例外発生の防止に使うのは間違った使い方

C#でTryParseは安全な型変換のために使われますが、単に例外を回避するためだけに使っているケースがあります。今回はTryParseのアンチパターン(悪い使い方)と正しい使い方について書きます。

TryParseの間違った使い方

問題回避の先延ばし

string str = null;
int value;
int result = Calculator(10, int.Parse(str));
    
...
    
private void Calculator(int val1, int val2)
{
   return val1 / val2;
}

当然なのですが、上記のコードは落ちます。この問題を回避するために下記のように書いているコードがありました。

string str = null;
int value;
int.TryParse(str, out value);
int result = Calculator(10, value);
    
...
    
private void Calculator(int val1, int val2)
{
   return val1 / val2;
}

確かに先のコードで落ちていたところでは落ちません。でもその先で落ちます。このコードだったら気づきますが、パースの結果を変数に格納し、後で使う場合は気づきません。これなら前者のコードで落ちたほうがバグに気づくのでマシです。これは実際に本当にあったケースです。

続きを読む

ViewBoxの中に設置したWrapPanelを親の親であるScrollViewerの横幅でレスポンシブルにさせる

XAMLのViewBoxを使うとViewのズームやズームアウトを導入することができます。このScrollViewerの中にViewBoxを配置し、更にViewBoxの中にWrapPanelを配置したときにWrapPanelをWindowやPageのサイズに合わせて機能させる方法について書き残します。

問題の現象

WrapPanel内のコンポーネントが改行しない

<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
    <Viewbox Stretch="None">
       <WrapPanel>
           <Button x:Name="button1" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button2" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button3" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button4" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button5" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button6" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button7" Content="Button" Width="75" Margin="10"/>
           <Button x:Name="button8" Content="Button" Width="75" Margin="10"/>
       </WrapPanel>
    </Viewbox>
</ScrollViewer>

このように書いてしまうと、WrapPanelの幅に制約がないのでButtonは改行なしで横一直線に並んでしまいます。
f:id:hazakurakeita:20160327004650p:plain

続きを読む

XAMLのBindingが途中で動かなくなる現象の原因を調べてみました

XAMLにBindingしていたプロパティが、あるタイミング以降に変更が反映されなくなる現象に遭遇しました。原因はBindingしているプロパティに値を直接代入するコードが実行されていたためでした。

問題の現象の詳細

問題のコード

<TextBlock Text="{Binding Message}" Name="Message"/>

XamlでViewModelのプロパティにBindingしていたのですが、

private void LostFocus(object sender, EventArgs e)
{
  Message.Text= "Hello";
}

xaml.csのイベントでもBindingしているプロパティを操作していました。

続きを読む

はてなブログのフォトライフにアップロードした画像を一括ダウンロードするWPFアプリケーションをC#で開発しました

f:id:hazakurakeita:20160719010426p:plain
はてなブログにはブログのバックアップサービスがありますが、残念ながら画像ファイルのバックアップはできません。アップロードした画像はフォトライフというサービスで管理されているのですが、ブログと違うサービスのせいなのか、連動してバックアップを取れません。フォトライフにも一括ダウンロード機能はありません。今回は一括ダウンロードアプリケーションを開発しました。

FotoLifeDownLoaderについて

動作環境

  • Windows10
  • .NET Framework 4.5以上

Windows10で開発と動作確認を行っていますが、Windows7や8.1でも動くと思います。Windows7は別途.NET Framework4.5をインストール必要があります。

続きを読む

抽象クラス(Abstract)とインターフェース(Interface)の違いと実装の使い分けについて

C#やJavaなどのオブジェクト指向型プログラミング言語に用意されている抽象クラスとインターフェースですが、コード的な違いは理解していても使い分けまでできているケースは意外と少ないです。中級未満のエンジニアだと、そもそも抽象クラスやインターフェースを使うことすらしない場合も。決して自分が完璧に理解し、使いこなしていると自負しているわけではないのですが、中級者レベルの理解と使い分けについてまとめます。

抽象クラスとインターフェースの違い

文法的(コード)な違い

  • インターフェースは実装を持てないが、抽象クラスは実装を持つことができる
  • 具象クラスはインターフェースを複数実装できるが、抽象クラスは1つしか継承することができない

教科書的には上記が主な違いになります。ただ、上記の文法的な違いだけを根拠に使い分けているとコードは保守性、拡張性を落としてしまいます。

  • 異なる実装の処理を同じインスタンスとして使用したいからインターフェースを使う
  • 処理を共通化したいから抽象クラス使う
  • 複数使いたいからインターフェースを使う

この考えはオブジェクト指向ではありません。

続きを読む

SwiftにはByte型がないので戸惑うけれどUInt8がバイトやバイト配列を担っています

むかしむかしのSwiftにはByte型があったと思うのですが、あるとき突然廃止されました。Byte型の配列など使って低級の処理をしていたアプリやプログラムはコンパイルすらできない事態に。C#やJavaからやってきた人も一瞬戸惑うのではないでしょうか。どう調べれば良いかも分からないのですが、結論から言うとUInt8型がByte型に相当するっぽいです。

Swiftのバイト配列

そもそも高級言語でバイト配列を触るなという話かもしれませんが、IoTやっていると触る必要が出てくるものですよね。相手がバイト配列でコマンドを定義していたりするので。例えばC#だと下記のようなバイト配列を定義できます。

var byteArray = new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }

これをSwiftで書くとこうなります。

let byteArray: [UInt8] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]

こういう痒い所の情報がなかなか出てこないSwiftはまだまだ辛いですなあ。

Swiftでバイト配列(UInt8配列)の変数どうしで論理演算XORを計算する方法

Swiftで[UInt8]のままXORをする必要がありました。しかしSwiftのXOR演算子は「^」であり、下記のように書いてもコンパイルエラーとなります。

let first: [UInt8] = [0xff, 0x10, 0xa0]
let second: [UInt8] = [0x01, 0x02, 0x03]
let result: [UInt8] = first ^ second

まあ、当然ですね。「^」演算子を使う場合はInt型の変数である必要があります。

ループでUInt8ペアを全てXORすればいい

かといって、[UInt8]を10進数に変換して演算子を使ってXORすると、Int型から[UInt8]に戻すのが大変です。しばらく格闘してみましたが、挫折しました。で、息抜きしていたら「UInt8ペアをXORしていけばいいやん」と気づきました。

public static func xor(first: [UInt8], second: [UInt8]) -> [UInt8] {
    var answer = [UInt8](repeating: 0x00, count: first.count)
        
    for index in 0..<first.count {
        answer[index] = first[index] ^ second[index]
    }
        
    return answer
}

いやほんとプログラミングの成果って時間に比例しないですねー。息抜きしたり休んだほうが圧倒的に短い時間で仕事終わったりしてしまう。。。