質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

2549閲覧

Swiftコードのリファクタリングの考え方が分かりません

besco

総合スコア11

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2017/11/14 09:06

プログラミング初心者です。
自分でも理解できていない部分が多いので、質問が分かりづらかったらすいません。

SwiftでmacOSアプリを開発しています。

Swiftコードのリファクタリングをしたいのですが、考え方ややり方が分からなくて困っています。
できれば考え方を教えていただきたいと思っています。

swift4

1 //パラメーター1 2 @IBOutlet weak var samp: NSTextField! 3 @IBAction func sampling(_ sender: NSSlider) { 4 samp.stringValue = String(format: "%.0f",sender.doubleValue) 5 } 6 @IBOutlet weak var sampling: NSSlider! 7 @IBAction func samp(_ sender: Any) { 8 sampling.stringValue = String((sender as AnyObject).doubleValue) 9 } 10 //パラメーター2 11 @IBOutlet weak var flame: NSTextField! 12 @IBAction func flamePriod(_ sender: NSSlider) { 13 flame.stringValue = String(format: "%.0f",sender.doubleValue) 14 } 15 @IBOutlet weak var flamePriod: NSSlider! 16 @IBAction func flame(_ sender: Any) { 17 flamePriod.stringValue = String((sender as AnyObject).doubleValue) 18 } 19 //パラメーター3 20 @IBOutlet weak var alp: NSTextField! 21 @IBAction func allPass(_ sender: NSSlider) { 22 alp.stringValue = String(format: "%.2f",sender.doubleValue) 23 } 24 @IBOutlet weak var allPass: NSSlider! 25 @IBAction func alp(_ sender: Any) { 26 allPass.stringValue = String((sender as AnyObject).doubleValue) 27 } 28 29 //ボタン1 30 @IBAction func defaultA(_ sender: Any) { 31 samp.stringValue = "38000" 32 sampling.stringValue = "38000" 33 flame.stringValue = "300" 34 flamePriod.stringValue = "300" 35 alp.stringValue = "0.5" 36 allPass.stringValue = "0.5" 37 } 38 39 //ボタン2 40 @IBAction func defaultB(_ sender: Any) { 41 samp.stringValue = "40000" 42 sampling.stringValue = "40000" 43 flame.stringValue = "200" 44 flamePriod.stringValue = "200" 45 alp.stringValue = "0.6" 46 allPass.stringValue = "0.6" 47 } 48 49 //ボタン3 50 @IBAction func defaultC(_ sender: Any) { 51 samp.stringValue = "48000" 52 sampling.stringValue = "48000" 53 flame.stringValue = "240" 54 flamePriod.stringValue = "240" 55 alp.stringValue = "0.55" 56 allPass.stringValue = "0.55" 57 }

やりたいこと①:IBOutletやIBactionをまとめたい
iOSではOutletcorrectionというものがあり、Outlet接続はまとめることができると検索によりわかったのですが、macOSでもそれに準ずるやり方はないのでしょうか?

やりたいこと②:ボタン1〜3をリファクタリングしたい
ボタン1〜3には上記のように定数を代入するのですが、これをきれいにまとめることはできないでしょうか?
実際にはパラメーターが10個ほどあるので、配列や関数を使ってリファクタリングしたいと思っているのですが、考え方が分からなくて困っています。。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

macOSに関してはよく知らない&Storyboardをみていないので軽めの回答になりますが、

  1. リーダブルコードという本を読む
  2. 命名を見直す
  3. パラメーター1〜3を抽象化したcustomViewを作る
  4. ボタン1〜3内のプロパティ代入を抽象化した関数を作る
  5. IBAction内のメソッドは可能な限り1行に纏める
  6. enumに定数を纏める

くらいは自分ならやります。
多分このコードが書かれてる場所ってViewController内だと思うのですがあまりにコード量が大きくなりすぎたら別クラスを作って分けたりもします。

リファクタリングに関して、というわけではありませんが素晴らしい記事があったのでリンクも貼っておきます。個人的には一番のお気に入りで何度も見返してます。

https://academy.realm.io/jp/posts/tryswift-daniel-steinberg-blending-cultures/

googleで「文化を調和させる swift」で調べるとこの記事を実践された方のコードがgithubにも上がってます。そちらも合わせて読むと分かりやすいですよ。

###追記

誰か何か回答してくれるんじゃないかと期待しながらちょくちょく見てたのですがあまり反応がないのでこっそり投稿です。とは言ってもmacOSは上記の通り門外漢ですのでiOSで同じような構成をとってます。またよく分からなかったものに関しては置き換え等々しています(senderの型のAnyとか)。なお命名に関してはかなりザル管理です。ここもっと良く出来るんじゃね?みたいな部分がありましたらご教授いただけましたら嬉しいです(とっても)。もし参考になれば幸いです。

Storyboard
ParameterView.xib

swift

1 2//(6) 3enum containerType: Int { 4 case top = 0 5 case middle = 1 6 case bottom = 2 7} 8 9enum propertyType { 10 case typeA 11 case typeB 12 case typeC 13} 14 15enum properties { 16 17 static let defaultA: [String] = ["38000", "300", "0.5"] 18 static let defaultB: [String] = ["40000", "200", "0.6"] 19 static let defaultC: [String] = ["48000", "240", "0.55"] 20 21 static func pickupPropertiesSet(_ property: propertyType) -> (containerType) -> String { 22 switch property { 23 case .typeA: 24 return { (controller: containerType) -> String in 25 return defaultA[controller.rawValue] 26 } 27 case .typeB: 28 return { (controller: containerType) -> String in 29 return defaultB[controller.rawValue] 30 } 31 case .typeC: 32 return { (controller: containerType) -> String in 33 return defaultC[controller.rawValue] 34 } 35 } 36 } 37} 38

swift

1class ViewController: UIViewController { 2 3 var myFunctions = Functions() 4 5 //IBOutletCollection 三つ程度なら普段ならまとめたりしないです。 6 @IBOutlet var containers: [ParameterContainerView]! 7 8 //(5) 9 @IBAction func tapDefaultA(_ sender: UIButton) { 10 myFunctions.defaultProperties(typeOf: .typeA, containers: containers) 11 } 12 @IBAction func tapDefaultB(_ sender: UIButton) { 13 myFunctions.defaultProperties(typeOf: .typeB, containers: containers) 14 } 15 @IBAction func tapDefaultC(_ sender: UIButton) { 16 myFunctions.defaultProperties(typeOf: .typeC, containers: containers) 17 } 18 19 override func viewDidLoad() { 20 super.viewDidLoad() 21 } 22 23 override func didReceiveMemoryWarning() { 24 super.didReceiveMemoryWarning() 25 } 26 27}

swift

1//(3) 2class ParameterView: UIView { 3 4 @IBOutlet weak var firstTextField: UITextField! 5 @IBAction func actionFirstTextField(_ sender: UISlider) { 6 firstTextField.text = String(sender.value) 7 } 8 9 @IBOutlet weak var secondTextField: UITextField! 10 @IBAction func actionSecondTextField(_ sender: UISlider) { 11 secondTextField.text = String(sender.value) 12 } 13 14 func fill(with text: String) { 15 firstTextField.text = text 16 secondTextField.text = text 17 } 18 19}

swift

1//(3) 2class ParameterContainerView: UIView { 3 4 override init(frame: CGRect) { 5 super.init(frame: frame) 6 load() 7 8 } 9 10 required init?(coder aDecoder: NSCoder) { 11 super.init(coder: aDecoder) 12 load() 13 } 14 15 func load() { 16 if self.subviews.isEmpty { 17 let testView = ParameterView.instantiate() 18 testView.frame = self.bounds 19 self.addSubview(testView) 20 } 21 } 22 23 open func accessParameterView() -> ParameterView { 24 guard let view = self.subviews.first as? ParameterView else { 25 fatalError() 26 } 27 return view 28 } 29 30 func fill(with text: String) { 31 let parameterView = accessParameterView() 32 parameterView.fill(with: text) 33 } 34 35}

swift

1 2extension NSObject { 3 class var className: String { 4 return String(describing: self) 5 } 6 7 var className: String { 8 return type(of: self).className 9 } 10} 11 12protocol NibInstantiatable {} 13extension UIView: NibInstantiatable {} 14 15extension NibInstantiatable where Self: UIView { 16 static func instantiate(withOwner ownerOrNil: Any? = nil) -> Self { 17 let nib = UINib(nibName: self.className, bundle: nil) 18 return nib.instantiate(withOwner: ownerOrNil, options: nil)[0] as! Self 19 } 20} 21

swift

1class Functions { 2 3 private func callDefaultProperty(_ propertyType: propertyType) -> ((containerType) -> String) { 4 return properties.pickupPropertiesSet(propertyType) 5 } 6 //(4) 7 func defaultProperties(typeOf type: propertyType, containers: [ParameterContainerView]) { 8 containers.enumerated().forEach { (index, container) in 9 let text = callDefaultProperty(type)(containerType(rawValue: index)!) 10 container.fill(with: text) 11 } 12 } 13 14 15}

投稿2017/11/14 13:33

編集2017/11/18 10:57
xAxis

総合スコア1349

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

besco

2017/11/15 00:48

ありがとうございます! ヒントを頂けただけでも助かります。 今の私のレベルでは難しい内容ですが、アドバイス内容を調べて頑張って自分でできるようにします。
besco

2017/11/20 01:29

xAxisさん、本当にありがとうございました。 まだプログラミングを始めて日も短く勉強不足なため、折角書いていただいたコードをまだ理解しきれていないのが申し訳ないです(汗) 理解できそうな部分を参考にさせていただきつつ、以下のように書き直しました。 ```swift //Outletのパラメーターを纏めた @IBOutlet weak var samp, flame, alp: NSTextField! @IBOutlet weak var sampling, flamePriod, allPass : NSSlider! //パラメーター1 @IBAction func sampling(_ sender: NSSlider) { samp.stringValue = String(format: "%.0f",sender.doubleValue) } @IBAction func samp(_ sender: Any) { sampling.stringValue = String((sender as AnyObject).doubleValue) } //パラメーター2 @IBAction func flamePriod(_ sender: NSSlider) { flame.stringValue = String((sender as AnyObject).doubleValue) } @IBAction func flame(_ sender: Any) { flamePriod.stringValue = String((sender as AnyObject).doubleValue) } //パラメーター3 @IBAction func allPass(_ sender: NSSlider) { alp.stringValue = String(format: "%.2f",sender.doubleValue) } @IBAction func alp(_ sender: Any) { allPass.stringValue = String((sender as AnyObject).doubleValue) } //ボタンのパラメーター設定を辞書型で記述 @IBAction func defaultButton(_ sender: NSButton) { var defaultparam:[String:[String:String]] = [ "ボタン1" : ["samp": "38000", "flame": "300","alp": "0.5"], "ボタン2" : ["samp": "40000", "flame": "200","alp": "0.6"], "ボタン3" : ["samp": "48000", "flame": "240","alp": "0.55"] ] //switch文でボタン管理 switch sender.title { case "ボタン1": samp.stringValue = defaultparam["ボタン1"]!["samp"]! sampling.stringValue = defaultparam["ボタン1"]!["samp"]! flame.stringValue = defaultparam["ボタン1"]!["flame"]! flamePriod.stringValue = defaultparam["ボタン1"]!["flame"]! alp.stringValue = defaultparam["ボタン1"]!["alp"]! allPass.stringValue = defaultparam["ボタン1"]!["alp"]! case "ボタン2": samp.stringValue = defaultparam["ボタン2"]!["samp"]! sampling.stringValue = defaultparam["ボタン2"]!["samp"]! flame.stringValue = defaultparam["ボタン2"]!["flame"]! flamePriod.stringValue = defaultparam["ボタン2"]!["flame"]! alp.stringValue = defaultparam["ボタン2"]!["alp"]! allPass.stringValue = defaultparam["ボタン2"]!["alp"]! case "ボタン3": samp.stringValue = defaultparam["ボタン3"]!["samp"]! sampling.stringValue = defaultparam["ボタン3"]!["samp"]! flame.stringValue = defaultparam["ボタン3"]!["flame"]! flamePriod.stringValue = defaultparam["ボタン3"]!["flame"]! alp.stringValue = defaultparam["ボタン3"]!["alp"]! allPass.stringValue = defaultparam["ボタン3"]!["alp"]! default : break } } ```
xAxis

2017/11/20 02:09

最初に書かれたコードよりも良くなってますよ。全体を俯瞰した時前よりもテンポよく読めます。また気になる点がありましたら遠慮なくどうぞ。
besco

2017/11/21 00:35

xAxisさん!ご親切にありがとうございました! アドバイス頂いたことをまだ理解できていない部分も多いので、とりあえずはアドバイスいただいた用語や意味を調べつつ、コードを見直すという形でリファクタリングしてみようと思います。 わかりづらい質問に丁寧に回答頂きましてありがとうございました。 とても助かりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問