1.3K Views
February 01, 24
スライド概要
シンプレクスグループの社内カンファレンスSimplex Tech Dayにて、発表された「『副作用』を理解してコードの質を上げよう」の発表資料です。
シンプレクスは1997年の創業以来、メガバンクや大手総合証券を筆頭に、日本を代表する金融機関のテクノロジーパートナーとしてビジネスを展開してきました。現在では、金融領域で培った豊富なノウハウを活用し、金融機関以外の領域でもソリューションを展開しています。2019年3月にはAI企業のDeep Percept株式会社、2021年4月には総合コンサルティングファームのXspear Consulting株式会社がグループに加わり、創業時より付加価値の創造に取り組んできたシンプレクスとワンチームとなって、公的機関や金融機関、各業界をリードする企業のデジタルトランスフォーメーション(DX)の推進を支援しています。
「副作用」(と「イミュータブル」)を理解してコードの質を上げよう 村上拓也 © 2023 Simplex Inc.
about me 村上拓也 2012年シンプレクス新卒, 2016年退職, 2021年に契約社員として復帰 SD コンピテンシー運営 & 内定者・新人 研修 の企画・実施 © 2023 Simplex Inc.
副作用 (Side Effect) と, 純粋な関数 (Pure Function) 副作用とは、関数 (またはメソッド) の外部に存在する状態に対して操作を行うこと。 副作用を起こさず、また外部の状態に依存せずに入力のみで出力が決まる関数を 純粋な関数 という。 © 2023 Simplex Inc.
純粋な関数の例
...
/** SNAKE_CASE into PascalCase */
String snakeToPascal(String str) {
return Arrays.stream(str.split("_"))
.map(word ->
word.substring(0, 1).toUpperCase()
+ word.substring(1).toLowerCase()
)
.collect(Collectors.joining())
}
...
© 2023 Simplex Inc.
「副作用」と「非純粋さ」に溢れたクラス
class CoffeeStand {
private BigDecimal dailySales = BigDecimal.ZERO;
private CoffeeRepository repo;
/**
*
coffee
* <p>
*
Wallet
*
Cop of Coffee
* */
public Cup buy(Wallet wallet, long coffeeId) {
Coffee coffee = repo.findById(coffeeId);
wallet.reduce(coffee.price);
this.dailySales = this.dailySales.plus(coffee.price);
SalesEventPublisher.publish(
String.format("
[
: %s]", coffee.name)
);
return new Cup(coffee.bean);
}
引数で与えられた
引数に与えられた
お客様に渡す
の購入処理を行う。
から残高を引き落とし、
を生成する。
コーヒーの売上がありました。 品目
}
//
public BigDecimal getTodaysSales() { return this.dailySales; }
© 2023 Simplex Inc.
「副作用」と「非純粋さ」に溢れたクラス: どこにある?
class CoffeeStand {
private BigDecimal dailySales = BigDecimal.ZERO;
private CoffeeRepository repo;
/**
*
coffee
* <p>
*
Wallet
*
Cop of Coffee
* */
public Cup buy(Wallet wallet, long coffeeId) {
Coffee coffee = repo.findById(coffeeId);
//
:
wallet.reduce(coffee.price);
//
: wallet
this.dailySales = this.dailySales.plus(coffee.price); //
:
SalesEventPublisher.publish(
String.format("
[
: %s]", coffee.name)
);
//
:
return new Cup(coffee.bean);
}
引数で与えられた
引数に与えられた
お客様に渡す
の購入処理を行う。
から残高を引き落とし、
を生成する。
コーヒーの売上がありました。 品目
メソッド単位では非純粋 メソッド外部の状態に依存
}
非純粋 外部の状態に依存
副作用
の状態変更
副作用 自身の状態変更
副作用 外部へのメッセージ送信
//
:
public BigDecimal getTodaysSales() { return this.dailySales; }
© 2023 Simplex Inc.
「副作用を排除した実装」や「純粋な関数」を目指すメリット 挙動の予測をしやすい テストやデバッグが容易になる 並行・並列処理に強い ただしデメリットも理解するべし かえって煩雑・冗長になるケースもある パフォーマンスが問題になるケースもある © 2023 Simplex Inc.
実務(業務システム開発)でも、副作用は排除すればよいのか? そんなことはない! (というか無理) 業務システムは「状態」を扱う仕事 取引の状態 ユーザーの状態 ジョブの状態 等々… 業務システムは副作用のかたまり データベースとの I/O も副作用 他システムへの連携も副作用 ログ出力も副作用 © 2023 Simplex Inc.
結局はバランス 許容する副作用と許容しない副作用を明確にする 例1) 引数オブジェクトのミューテーションは避ける 例2) グローバルな変数の定義や操作は避ける 副作用の起きる場所を限定 (隔離) する 例) データベース入出力が起きるレイヤを限定する 副作用に関する不確実性を排除する 例) 副作用のある箇所はモックしてテストできるようにする © 2023 Simplex Inc.
副作用隔離のヒント①: 同心円状アーキ テクチャ 外側が内側に依存するアーキテクチャ 中心にある Entity や UseCase は比較的純 粋・イミュータブルな実装をしやすい 外側の層は外界とのインターフェイスを担当 し、副作用が多い © 2023 Simplex Inc.
副作用隔離のヒント②: Redux Redux (https://redux.js.org/) はフロントエンドア プリの状態管理フレームワークのひとつ。 Redux の Core Concepts and Principles に、以下 の3つが提示されている Single Source of Truth State is Read-Only Changes are Made with Pure Reducer Functions © 2023 Simplex Inc.
本編 fin. © 2023 Simplex Inc.
Appendix: イミュータブル (Immutable) オブジェクトや値が、その生成後に変更できないこと。 © 2023 Simplex Inc.
Which is Immutable? public class Person { // Mutable private Long id; private String name; private int age; public Person() { } // getters and setters... } public class Person { // Immutable private final Long id; private final String name; private final int age; public Person(Long id, String name, int age) { ... } // getters... } © 2023 Simplex Inc.
Appendix: イミュータブルをデフォルトとする言語仕様や慣習 © 2023 Simplex Inc.
再代入不可な変数を標準とする言語 Java では変数を再代入不可とするために final 修飾子を「わざわざ」付与する必要がある が、逆に再代入不可を標準とする言語も多い。 や では は再代入不可、var は再代入可能。 この行はコンパイルエラー // Kotlin Scala val val name = "Tsuruoka" name = "Yamamoto" // は で再代入不可、let で再代入可能 なら実行時エラー、TSならコンパイル時エラー // JS/TS const const age = 34; age = 23; // JS © 2023 Simplex Inc.
Java の record Java 16 にて record が正式リリースとなった。record はイミュータブルである。 record Point(double x, double y) { } var point = new Point(3, 4); System.out.println(point.x()); // point.setX(8); // setter プロパティ取得は getXXX ではなくプロパティ名がそのままメソッド名に は定義されないのでコンパイルエラー © 2023 Simplex Inc.
Ruby: 破壊的なメソッドの命名に ! を付す Ruby では、メソッド名に ! を利用できる。慣習として、メソッドのレシーバーとなるオブ ジェクトが変更されるメソッドに ! をつけるものとされている。 irb> name = "Trevor Bauer" => "Trevor Bauer" irb> name.upcase # => "TREVOR BAUER" irb> name => "Trevor Bauer" # irb> name.upcase! => "TREVOR BAUER" irb> name => "TREVOR BAUER" # `upcase!` 大文字化したものを取得 元のオブジェクトは変わっていない © 2023 Simplex Inc. を呼んだ後はオブジェクト自体変化している
画像の出典 表紙: https://unsplash.com/photos/MBfYGVsDEp8 The Clean Architecture: https://blog.cleancoder.com/uncle-bob/2012/08/13/theclean-architecture.html Redux: Core Concepts and Principles: https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#coreconcepts-and-principles © 2023 Simplex Inc.
全部 fin. © 2023 Simplex Inc.