ソフトウェア概論
自学用メモ。
関数の副作用
関数や手続きがシステムの状態を変える現象を指す。また、システム外部とのやり取りも含む。 具体的な例:
- グローバル変数の変更:関数内でグローバル変数の値を変える。
- ファイル操作:ファイルの読み込みや保存。
- データベース操作:データの追加、更新、削除など。
- 画面出力:ユーザーインターフェースへの情報の表示。
純粋関数と副作用
純粋関数:与えられた入力に基づいて結果を返すだけの関数。外部の状態に影響を与えない。この性質のため、テストがしやすく、再利用性が高い。また、動作が予測しやすい。
副作用の利点・欠点:
- 利点:実際のアプリケーションでは、データの永続化やユーザーとのインタラクションなど、副作用が必要な操作が多い。これにより、具体的なタスクを実行する。
- 欠点:副作用を持つ関数は、他の部分との依存関係が強くなることがある。そのため、テストやデバッグが難しくなることがある。また、不具合の原因となることも。
副作用のある関数のテスト方法
Mock:副作用を持つ部分(例:データベースや外部API)を模倣する。これにより、テスト中に実際の副作用を発生させずに関数の動作を検証できる。モックを使うと、特定の戻り値やエラーを模倣して、関数の反応をテストすることができる。
Stub:関数やメソッドの実装を一時的に置き換える。これにより、テスト中の動作を制御できる。スタブを使うと、関数がどのように呼び出されたか、何回呼び出されたかなどの情報を取得できる。
Spy:関数やメソッドの呼び出しを監視する。スパイを使うと、関数がどのように、何回、どの引数で呼び出されたかを検証できる。
状態のリセット:テストの前後でシステムの状態を初期化する。これにより、一つのテストが他のテストの結果に影響を与えることを防ぐ。
統合テスト:システムの複数の部分が連携して正しく動作するかを検証する。モックやスタブを使わず、実際の環境での動作を確認する。
値渡しと参照渡しの違いとその影響(Go言語の例)
プログラミングにおいて、関数やメソッドに変数を渡す方法として、値渡しと参照渡しがある。これらの方法は、関数内での変数の扱い方や副作用の有無に影響を与える。
1. 値渡し (Pass by Value)
- 引数として変数のコピーが関数に渡される。
- 関数内で引数の値を変更しても、関数の外部にある元の変数の値は変わらない。
- 副作用が発生しづらい。
例:
package main
import "fmt"
func f(x int) {
x = 10
}
func main() {
a := 5
f(a)
fmt.Println(a) // 5が出力される
}
struct
の場合も同様。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func f(p Person) {
p.Name = "Modified"
p.Age = 100
}
func main() {
person := Person{Name: "Original", Age: 20}
f(person)
fmt.Println(person) // {Original 20} が出力される
}
2. 参照渡し (Pass by Reference)
- 引数として変数のアドレス(ポインタ)が関数に渡される。
- 関数内で引数を通じて変数の値を変更すると、関数の外部にある元の変数の値も変わる。
- 副作用が発生する可能性が高まる。
例:
package main
import "fmt"
func f(x *int) {
*x = 10
}
func main() {
a := 5
f(&a)
fmt.Println(a) // 10が出力される
}
struct
の場合も同様に、フィールドの値を変更することができる。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func f(p *Person) {
p.Name = "Modified"
p.Age = 100
}
func main() {
person := Person{Name: "Original", Age: 20}
f(&person)
fmt.Println(person) // {Modified 100} が出力される
}
速度の観点
データの大きさと速度:
- 基本型(int, float, boolなど)や小さなデータ構造では、値渡しは効率的。
- しかし、大規模なデータ構造や配列の場合、データのコピーが必要になるため、速度が落ちる可能性がある。この時、ポインタ渡しの方が効率的に動くことが多い。
関数の呼び出し頻度:
- 関数が頻繁に呼ばれる場面では、値渡しでのデータのコピーが増える。その結果、パフォーマンスに影響が出るかもしれない。
- ポインタ渡しを利用することで、このようなオーバーヘッドを減少させ、パフォーマンスを向上させることができる。
メモリのアクセスパターン:
- ポインタを利用すると、メモリの異なる位置を参照することが増え、キャッシュミスが起きやすくなる。
- 一方、値渡しは連続したメモリ領域を使うため、キャッシュの効率が上がることがある。
データの変更とサイドエフェクト:
- ポインタ渡しを利用する場合、関数内でのデータの変更が元のデータに影響する。このため、サイドエフェクトに注意が必要。
- 値渡しの場合、元のデータは変わらないため、このような心配は不要。