$30 off During Our Annual Pro Sale. View Details »

MaintainabilityIndexを 計測しながらコードの保守性を 改善している話

toya108
September 28, 2022

MaintainabilityIndexを 計測しながらコードの保守性を 改善している話

toya108

September 28, 2022
Tweet

Other Decks in Technology

Transcript

  1. After Party iOSDC Japan 2022 toya108 MaintainabilityIndexを 計測しながらコードの保守性を 改善している話 1

  2. • 大体toya108という名前でやってます • Cookpad Inc. iOS Developer • クライミングが好き •

    3週間前に父になりました 自己紹介 2
  3. • MaintainabilityIndexが何か分かる • MaintainabilityIndexを計測するメリットが分かる • MaintainabilityIndexの計測方法が分かる 今日のゴール 3

  4. • クイズ • MaintainabilityIndexとは • MaintainabilityIndexを測るメリット • どうやってMaintainabilityIndexを計測しているか • まとめ

    アジェンダ 4
  5. クイズ 5

  6. Q. ios-cookpadのレポジトリの中で特にメンテナンスが難 しい箇所はどこでしょうか? (file、またはmodule単位で回答) 突然ですがクイズです 6

  7. A.多分みんな分からない 7

  8. Q.なぜ分からないか A.読んだことがないから 8

  9. でもそれって結構困るのでは? • 読まれずに放置されてたコードは保守しやすい状態になってるのか分からな い • 放置されていたコードに突然の変更が必要になる • 辛い ということが起きるかも。。。 読んだことがないコードは保守しやすいかどうか分か

    らない 9
  10. • どこから読んでいくのか • 巨大なコード群が日々更新される • メンバーも定期的に入れ替わる • 読んだ箇所も解像度が人によって違う ◦ 実装者しか把握していない難しさがあったりする

    じゃあ全部読みます!は実質不可能 10
  11. コードを読まなくても 保守しやすい状態かどうか 知る方法があればいいのでは? MaintainabiltyIndexを 測ってみよう! 11

  12. MaintainabilityIndexとは 12

  13. • 保守容易性指数、つまりコードがどれくらい保守しやすいかを表す指標の こと • VSCodeで一部の言語で計測可能 ◦ https://learn.microsoft.com/ja-jp/visualstudio/code-quality/code-me trics-maintainability-index-range-and-meaning?view=vs-2022 • Dartでも計測できる

    ◦ https://dartcodemetrics.dev/docs/metrics/maintainability-index MaintainabiltyIndexとは 13
  14. • 0~100の数値で保守容易性を出す • 3段階でコードのヤバさを知ることができる MaintainabiltyIndexとは 14 0~9 ヤバい 10~19 やや危険

    20~100 大丈夫そう
  15. MaintainabilityIndexの計算式 Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead Volume)

    - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171) ※詳細は「どうやって MaintainabilityIndexの計測しているか」で説明するよ。 15
  16. 複雑そうだと思っていた 箇所は実際に赤になって おり、体感と一致してる! swiftのfileごとのMaintainabilityIndexを スプレッドシートで確認できるようにしている 16

  17. MaintainabilityIndexを 計測するメリット 17

  18. 隔週の技術改善MTGにて どこか改善したい箇所 ありますか? • 読んだことない箇所は改善す べきか分からないなあ • どこから読めばいいかも検討 つかないしなあ 思いつかないですね。

    同僚氏 わい 18
  19. MaintainabiltyIndexを見て 改善すべき箇所にあたりつける Legacy配下がヤバいな。 特にマイレシピ画面とか 0(最低値) になってる。 わい 19

  20. 読んだことがないコードでも手を入れるのが難しそうか ざっと把握できる。 MaintainabiltyIndexを測るメリット① 20

  21. マイレシピ画面の実態 • VIPER化されていないFatViewController • TableViewと旧時代のDataSourceの使用 • Legacyモジュール内の様々なコードへの依存 実際にコードを読んでみて実態を把握する ヤバすぎるっぴ! わい

    21
  22. コード生成を用いたiOSアプリマルチモジュール化のための依存解決 
 ※ios-cookpadのアーキテクチャについては以下 を参照ください。 22

  23. 次の技術改善MTGにて どこか改善したい箇所 ありますか? マイレシピ画面をリファクタしたいです。 同僚氏 わい お、ええで 23

  24. • FatViewControllerをVIPERSceneに分割する ◦ 他のコードとアーキテクチャが揃うので読みやすくなる ◦ コードが分割されてMaintainabilityIndexの数値も改善するはず • 元はLegacyの配下にあったが、独立したモジュールに切り出す ◦ 必要なものはCoreに移動し、それ以外はマイレシピのためのモジュールに閉じ込

    める ◦ コードが分割されてMaintainabilityIndexの数値も改善するはず ◦ 副次的にSandboxビルドで動作確認がしやすくなる • SwiftUIにする ◦ TableViewのIndexPath管理から脱却したい ◦ 最近チームでもSwiftUI化を積極的に行っているので リファクタの方針決め 24
  25. リファクタ後の計測結果 Before: 0 After: 14.57 file数は増えたが、その分 1fileあたりの コード量とロジック量が減らせている ※まだリリースはできてないけど こうなる予定

    25
  26. 変更の前後で以前より保守しやすくなったかどうかを 数値で確認できる。 MaintainabiltyIndexを測るメリット② 26

  27. • 読んだことがないコードでも手を入れるのが難しそうか ざっと把握できる。 • 変更の前後で以前より保守しやすくなったかどうかを 数値で確認できる。 MaintainabiltyIndexを測るメリットまとめ 27

  28. • 今回はfileごとに解析しているので、fileより小さい単位で保守が難しい コードを特定することはできない • コード量、ロジックの複雑さに起因しない難しさを評価することはできな い。(背景知識が必要なコードや古いOSで実装したコードなど) できないこともある 28

  29. 29 • Dartのdocumentでこのように明言されている ◦ https://dartcodemetrics.dev/docs/metrics/maintainability -index • 数値を過信して、コードを読まずに現在の状況を判断するのは良くなさそ う。 (注意)

    MaintainabilityIndexは実験的な 数値なため、過信することも禁物 注: 保守性指標はまだ非常に実験的な指標であり、   他の指標ほど真剣に考慮すべきではありません。
  30. どうやって MaintainabilityIndexを 計測しているか 30

  31. MaintainabilityIndexの計算式 Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead Volume)

    - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171) これをSwiftで計算している。 31
  32. swiftでMaintainabilityIndexを計算する let rebasedHalsteadVolume = 5.2 * log(Double(halsteadVolume)) let rebasedCyclomaticComplexity =

    0.23 * Double(cyclomaticComplexity) let rebasedLineOfCode = 16.2 * log(Double(lineOfCode)) let maitainabilityIndex = max(0, CGFloat(171 - rebasedHalsteadVolume - rebasedCyclomaticComplexity - rebasedLineOfCode) * 100 / 171) 32
  33. ①Halstead Volume Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead

    Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171) 33
  34. Halsteadさんが提唱したコードの複雑度を測るための指標の一つ https://dartcodemetrics.dev/docs/metrics/halstead-volume Halstead Volumeとは η1 = the number of distinct

    operators η2 = the number of distinct operands N1 = the total number of operators N2 = the total number of operands Program vocabulary: η=η1+η2 Program length: N=N1+N2 Volume: V=Nlog2η 34
  35. なんか色々書いてるけど、コードの中のoperatorとoperandの総数、そして それぞれの重複を弾いた個数が分かれば、あとは式に突っ込むだけ。 ※operator = 演算子のこと。+ とか - とか ※operand =

    被演算子のこと。1 + 1 の 1はoperandになる Halstead Volumeとは 35
  36. SwiftでHalsteadVolumeを計算する import Accelerate // logの使用に必要 let operators: [String] let distinctOperators:

    Set<String> let operands: [String] let distinctOperands: Set<String> let vocabulary= Float(distinctOperators.count + distinctOperands.count) let length = Float(operators.count + operands.count) let halsteadVolume = length * log2(vocabulary) 36
  37. Q. operatorとoperandの数は どうやってカウントするの? 37

  38. https://github.com/apple/swift-syntax • Swiftのソースコードを解析したり変換できるライブラリ • これを使ってコードの中のoperatorやoperandの数をカウントする SwiftSyntaxを利用する 38

  39. operatorとoperandの数をカウントする import SwiftSyntax import SwiftSyntaxParser // 計測用のVisitorを用意する class MaintainabilityIndexVisitor: SyntaxVisitor

    { var operators: [String] = [] var distinctOperators: Set<String> { Set(operators) } var operands: [String] = [] var distinctOperands: Set<String> { Set(operands) } … } 39
  40. operatorとoperandの数をカウントする override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind { switch

    node.tokenKind { case .spacedBinaryOperator, .unspacedBinaryOperator, .prefixOperator, .postfixOperator: operators.append(node.text) default: // Add non-reserved words to operands if !node.tokenKind.isKeyword && !node.text.isEmpty { operands.append(node.text) } } } 40
  41. visitorにsourceを食わせる let sourceFile: SourceFileSyntax = try SyntaxParser.parse(source: source) let visitor

    = MaintainabilityIndexVisitor() visitor.walk(sourceFile) 41
  42. visitorにsourceを食わせる // walk()の実行後にここに入ってくる。 override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind

    { switch node.tokenKind { case .spacedBinaryOperator, .unspacedBinaryOperator, .prefixOperator, .postfixOperator: operators.append(node.text) default: // Add non-reserved words to operands if !node.tokenKind.isKeyword && !node.text.isEmpty { operands.append(node.text) } } } 42
  43. ②Cyclomatic Complexity Maintainability Index = MAX(0,(171 - 5.2 * ln(Halstead

    Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171) 43
  44. • コードの分岐の数を合計し、コードの複雑度を測る指標のこと ◦ 循環的複雑度と訳される • 1からスタートし、分岐(if・for・switch・whileなど)の度に「1」を加算する • swiftlintのruleにも追加されている ◦ https://realm.github.io/SwiftLint/cyclomatic_complexity.h

    tml Cyclomatic Complexityとは 44
  45. Cyclomatic Complexityを計算する var cyclomaticComplecity = 1 override func visit(_ node:

    TokenSyntax) -> SyntaxVisitorContinueKind { switch node.tokenKind { case .ifKeyword, .switchKeyword, .forKeyword, .whileKeyword, .guardKeyword, .caseKeyword: cyclomaticComplecity += 1 case .identifier(let identifier): if identifier == "forEach" { cyclomaticComplecity += 1 } … 45
  46. • 加算する時のstatementは大体swiftlintと揃えている ◦ https://github.com/realm/SwiftLint/blob/4382ef49b9af08cfc1 3cc145e206a63e89e03367/Source/SwiftLintFramework/Rules /RuleConfigurations/CyclomaticComplexityConfiguration.swift #L15 • forEachだけ別にハンドリングしてるのはそのため •

    repeatはwhileとセットで一つの分岐なので加算してない • if case も一つの分岐なのでif と caseでそれぞれ加算したりは してない Cyclomatic Complexityを計算する 46
  47. ③Lines of Code Maintainability Index = MAX(0,(171 - 5.2 *

    ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171) 47
  48. • コードの行数のこと • 今回は空行を除いた論理LOCを使っている Line of Codeとは 48

  49. try CommandLine.arguments.forEach { argument in let url = URL(fileURLWithPath: argument)

    let fileData = try Data(contentsOf: url) let source = fileData.withUnsafeBytes { buf in return String(decoding: buf.bindMemory(to: UInt8.self), as: UTF8.self) } let lineOfCode = source.split(separator: "\n").count Line of Codeの出し方 • sourceを改行ごとに分割してcountする 49
  50. swiftでMaintainabilityIndexを計算する let rebasedHalsteadVolume = 5.2 * log(Double(halsteadVolume)) let rebasedCyclomaticComplexity =

    0.23 * Double(cyclomaticComplexity) let rebasedLineOfCode = 16.2 * log(Double(lineOfCode)) let maitainabilityIndex = max(0, CGFloat(171 - rebasedHalsteadVolume - rebasedCyclomaticComplexity - rebasedLineOfCode) * 100 / 171) print("\(sourcePath), \(halsteadVolume), \(cyclomaticComplexity), \(lineOfCode), \(maitainabilityIndex)") 50
  51. 51 https://github.com/toya108/MaintainabilityIndexCalculator 実装が気になる方はこちらをどうぞ

  52. Q. 出力したMaintainabilityIndexは どうやって取り回してるの? 52

  53. • コンマ区切りで出力した数値をス プレッドシートにコピペする • コンマごとに列を分割する • スプレッドシートの数式で MaintainabilityIndexごとに行 を色分けする 出力したMaintainabilityIndexの取り回し

    53
  54. • コンマ区切りで出力した数値をス プレッドシートにコピペする • コンマごとに列を分割する • スプレッドシートの数式でファイル ごとに色分けする 出力したMaintainabilityIndexの取り回し 温かみのある手作業!

    54
  55. • リリース用のビルドを作るコマンドと合体させて、リリースバージョンごとに S3かどこかにアップロードしておいて、ブラウザから確認できるようにする とか • やっていきが必要 (課題) 毎回手作業でスプレッドシートに移すのしんど いので何とかしたい 55

  56. • 前述したフローを行うハードルが高いので定期的に値を計測し、改善する 運用フローまでは作れていない • 計測した値を元にコードを改善することが目的のため、計測のハードルは 下げていきたい その他課題 56

  57. まとめ 57

  58. コードを読みにいかなくても 保守しやすい状態かどうか知りたい? MaintainabiltyIndexを 測ってみよう! 58

  59. • 読んだことがないコードでも手を入れるのが難しそうか ざっと把握できる。 • 変更の前後で以前より保守しやすくなったかどうかを 数値で確認できる。 MaintainabiltyIndexを測るメリットまとめ 59

  60. • コマンドラインからswiftのfileのpathを渡す • SwiftSyntaxで解析する • 式に従って計算して出力する • 温かみのある手作業によりスプレッドシートへまとめる MaintainabilityIndexの計測方法 60

  61. • https://learn.microsoft.com/en-us/visualstudio/code-quality/code-metric s-maintainability-index-range-and-meaning?view=vs-2022 • https://realm.github.io/SwiftLint/rule-directory.html • https://github.com/apple/swift-syntax • https://monoist.itmedia.co.jp/mn/articles/1010/19/news124.html •

    https://dartcodemetrics.dev/docs/metrics/maintainability-index • https://blog.yagipy.me/analyze-maintainability-index 参考資料 61