In computer science, an operation or expression is said to have a side effect if it has any observable effect other than its primary effect of reading the value of its arguments and returning a value to the invoker of the operation.
参照: Side effect (computer science)
ある操作や式が「引数の値を読み取り、呼び出し元に値を返す」という主要な効果以外に観測可能な効果を持つ場合、その操作や式は副作用を持つと言われる。
コンピューターサイエンスの文脈だと上記のように定義される。
コンピューターサイエンスの文脈では、特にプログラミング言語やそれらを使用した設計/実装の文脈で、副作用について言及されることが多い。
Nowadays, we define a side effect to be any state change that outlives the function, even if that state change is the primary intent of the function. In other words, a function with a side effect is impure.
Clean Code: A Handbook of Agile Software Craftsmanship, 2nd Edition 8章 では
と定義されている。
Clean Codeの内容を参考にしながら副作用について整理していくことにする。
例えば、RubyのArray#popメソッドは副作用を持つ関数と言える
array = [1, [2, 3], 4]
p array.pop # => 4
p array.pop # => [2, 3]
p array # => [1]
array.popによって、arrayに処理後の状態が残る。この残された状態が副作用。
副作用は「ソフトウェアに複雑さをもたらすから」。
具体的には、時間的結合(temporal coupling)と呼ばれる結合につながる。副作用を持つコードは、前後のコード間に「状態の変化」を生み出し、その状態の変化に依存する形でコードの実行順序を保証する必要が出る。
例: セマフォは取得してからでないと解放できない。
取得 → 解放 という処理の前提が生まれてしまう。
時間的結合により順番に依存することで、システム全体の挙動の理解やデバッグを難しくする。 局所的な順番依存がシステム全体の複雑性を上げ、不安定な状態につながってしまう。
以下のようなペア(片方は副作用を作成し、もう片方はそれを取り消す)を持つことが多い。
関数型言語の副作用の管理には役立つが、副作用を完全になくすことはできないことを頭に置いておくこと。 例えば、OSレベルの操作(ファイルI/Oなど)には必ず副作用が伴う。
以下は、副作用を局所化して隠蔽するコードの例。
countメソッドは副作用を隠蔽できていると言える
countメソッドの利用側のコードは、時間的結合を意識しなくても済むclass LineCounter
class << self
def count(file_name)
initialize_state(file_name)
count_the_lines
finalize
end
private
def initialize_state(file_name)
@n_lines = 0
@file = File.open(file_name)
end
def count_the_lines
@file.each_line do |_line|
@n_lines += 1
end
end
def finalize
@file.close
@n_lines
end
end
end