アプリ開発道場アドベントカレンダーのの14日目は@sohichiroが担当しまっす。
つい最近swiftがオープンソースになりましたが、みなさんiOSアプリの開発にはどのような言語を使用されていますでしょうか?
私も遅くなりましたがobjCからswiftへ移行いたしました。
swift。
書いていて思うのは、バグが入らないように、書きやすいように、などなどいろいろ親切な言語仕様になっていると感じています。
objective Cでは、
if (A){
//Aのときの処理
}else if (B){
//Bのときの処理
}else if (C){
//Cの時の処理、以下適当に続く
}
の文法をよく使っていたのですが、
なんか美しくないし、考慮漏れ多い
と常々感じておりました。
そんな中、swiftのswitch文と、条件にenumを用いることで、考慮すべき状態が抜けてしまうということは大幅に低減できるようになったのですが。。。
しかしながら、、、
美しくないのですよ。。
if else ifの文法は、言うに及ばず、switch文を使っても、どこかしっくりこない。。
ということで、最近私が読んだ本を、最初にご紹介してから、その中から条件分岐が美しく書けるような書き方を幾つかご紹介いたします。
#読んだ本:新装版 リファクタリングー既存のコードを安全に改善するー
amazonリンクはこちら
この本は、ざっくり言うと
・リファクタリングとは何か?
・メリットは?
・リファクタリングのやり方
・リファクタリングすべき箇所=不吉な匂いのする場所の見つけ方
・不吉な匂いのする場所をどうリファクタリングするか?を実例を交えて紹介
という流れが書かれています。
コードが汚いorz..というお悩みをお持ちの方は、一度お読みになることをオススメします。
それでは、この本に書いてあるリファクタリング手法の内、条件分岐の箇所をちょっと美しく書ける手法を僕なりにアレンジしてご紹介いたします。
最初から書くときに少し意識しておくと、ちょっとでも違うかなーと思っています。
#ガード節による入れ子条件記述の置き換え
いろいろ複雑な条件で、処理を分けること、あると思います。
お値段が入っていて、でも税率が入ってないとか。。
ここからここまで使用した場合のみ、割引が適用とか。。
たとえばこんな感じのコードいかがでしょう??
func getPayAmount(isDead:Bool, isSepareted:Bool, isRetired:Bool) -> {
var result:Double = 0//初期化
//引数のどれかがtrueになっていれば、それに応じた値を返す
if isDead {
result = deadAmount()
}
else {
if isSepareted {
result = separetedAmount()
}
else {
if isRetied {
result = retiredAmount()
}
else {
result = normalPayment()
}
}
}
return result
}
入れ子構造が深いっすね。。
これは、フラグそれぞれで処理を分けているだけで、どれか一個だけ処理が通ればOKっていうときのパターン。
こういうときは、さっさとreturn使って、処理を途中で切り上げてあげるように書くと見やすくなります。
こんな感じ。
func getPayAmountEasy(isDead:Bool, isSepareted:Bool, isRetired:Bool) -> Double {
if isDead {
return deadAmount()
}
if isSepareted {
return separetedAmount()
}
if isRetired {
return retiredAmount()
}
return normalPayment()
}
入れ子構造が浅くなって、どれか一個引数がtrueになっていれば、値を返す構造になっているというのが一見して分かるようになります。
switch文で分けても、同様の記法は可能になるかなと。
こんなかんじでしょうか。。
func getPayAmountEasySwitch(isDead:Bool, isSepareted:Bool, isRetired:Bool) -> Double {
switch true {
case isDead:
return deadAmount()
case isSepareted:
return separetedAmount()
case isRetired:
return retiredAmount()
default:
return normalPayment()
}
}
switch文の条件にtrueを入れてるっていうのは、あんま綺麗な形ではないかもですね。
次行きましょう
#ポリモーフィズムによる条件記述の置き換え
処理する対象によって、条件を分けるパターン。
あると思います。
使用者が、1)児童、2)生徒、3)学生、とか。。
とりあえず実例行ってみましょう。
class vehicle{
enum vehicleType:Int {
case car
case bike
case bus
}
var type:vehicleType = .car
func getSpeed() -> Int {
switch type {
case .car:
return getCarSpeed() - getTrafficAgainst()
case .bike:
return getBikeSpeed() - getTrafficAgainst() - getBikeAgainst()
case .bus:
return getBusSpeed() - getTrafficAgainst() - getExchangePassenger()
}
}
//case文の中身のメソッドの実装は省略
}
乗り物vehicleに対して、乗り物の種類によって、取得できる速度を変更しているパターンです。
こういうのは、こんな感じに、書き換えちゃうのはいかがでしょう。
class vehicleEasy{
//継承して使うことを前提にしたクラス
enum vehicleType:Int {
case car
case bike
case bus
}
var type:vehicleType
init(){
type = .car
}
//継承したクラスでオーバーライドする
func getSpeed() -> Int {
return 100
}
}
class car:vehicleEasy {
override init(){
super.init()
type = .car
}
override func getSpeed() -> Int {
return getCarSpeed() - getTrafficAgainst()
}
//getSpeed()内の実装メソッドは省略
}
class bike:vehicleEasy {
override init(){
super.init()
type = .bike
}
override func getSpeed() -> Int {
return getBikeSpeed() - getTrafficAgainst() - getBikeAgainst()
}
//getSpeed()内の実装メソッドは省略
}
class bus:vehicleEasy {
override init(){
super.init()
type = .bus
}
override func getSpeed() -> Int {
return getBusSpeed() - getTrafficAgainst() - getExchangePassenger()
}
//getSpeed()内の実装メソッドは省略
}
swichで条件分岐していた処理自体を、継承したクラスに移行し、継承したクラスを呼び分けることで、switch文自体を消してしまうというパターン。
それぞれの乗り物の処理を一つのクラスに閉じ込められるので、処理があっちいったりこっちいったりにならないのと、継承されたそれぞれのクラスからは、他の乗り物に関する処理が、見えないようになっているので、実際に使う継承したクラス自体はスッキリ。
protocolエクステンションなんかを使うと、さらに綺麗に書けるのかもしれません。
では次行ってみましょう。
#制御フラグの削除
繰り返し処理の途中で、何か条件に当てはまったときにフラグを立てておいて、あとで条件に合致したものを取り出すとか、あると思います。
例えばこんな感じとか。
func checkFunnyPeople(people:[String]) {
var found:Bool = false
for person in people {
if !found {
if person == "Sohichiro" {
alertFoundPeople()
found = true
}
if person == "Nagao" {
alertFoundPeople()
found = true
}
}
}
}
これだと、foundフラグが必要になるんですよね。
本から影響を受けた僕だと、こんな感じに書きます。
func checkFunnyPeople(people:[String]) {
for person in people {
if person == "Sohichiro" {
alertFoundPeople()
break
}
if person == "Nagao" {
alertFoundPeople()
break
}
}
}
foundフラグを、削除して、条件が当てはまった時点で処理を止めてしまうパターンです。
foundフラグが消えてるので、foundフラグがtrueになったら〜とか、falseになってるときは〜という処理を考慮しなくて良くなるので、ちょっとスッキリです。
コードを美しく書くというのは、ただ単に個人のこだわりという部分もあるかとは思います。
コードを書いているときにバグが入ってしまうことはしかたがないと思います。ですが、美しいコードを書いていると、処理の流れが追いやすいため、バグを起こしている箇所が早期に発見できたりしますので、コードを美しく書いておくことは、おすすめです。
とはいえ、美しいコードを追い求めても、終わりがないところでもあるので、程々に。