ContentSecurityPolicyの`build_directive`で`flat_map`を使用するリファクタリング
build_directiveメソッドがflat_mapを使うよう変更され、validateメソッド内での不要なflatten呼び出しが除去されました。あわせてbuild_directiveの戻り値がvalidateの戻り値に依存しない形に修正されています。
背景
validateメソッドはCSPディレクティブのソースに不正な文字(セミコロンや空白)が含まれていないかを検証する役割を持ちます。変更前はsources.flatten.eachと書かれており、validateがflattenの責務を兼ねていました。これは、build_directiveがmapで生成したネストした配列をvalidate側でならす設計になっていたことを意味します。
この構造では、build_directiveの戻り値がvalidateの戻り値(eachの戻り値、すなわちflatten後の配列)に暗黙的に依存していました。明示的なreturnがなく、Rubyの「最後の式が戻り値になる」という性質に頼っていたため、意図が読み取りにくいコードになっていました。
技術的な変更
build_directiveでのmapをflat_mapに変えることで、配列の平坦化をbuild_directive内で完結させています。
変更前:
def validate(directive, sources)
sources.flatten.each do |source|
if source.include?(";") || source != source.gsub(/[[:space:]]/, "")
raise InvalidDirectiveError, <<~MSG.squish
Invalid Content Security Policy #{directive}: "#{source}".
MSG
end
end
end
def build_directive(directive, sources, context)
resolved_sources = sources.map { |source| resolve_source(source, context) }
validate(directive, resolved_sources)
end
変更後:
def validate(directive, sources)
sources.each do |source|
if source.include?(";") || source != source.gsub(/[[:space:]]/, "")
raise InvalidDirectiveError, <<~MSG.squish
Invalid Content Security Policy #{directive}: "#{source}".
MSG
end
end
end
def build_directive(directive, sources, context)
resolved_sources = sources.flat_map { |source| resolve_source(source, context) }
validate(directive, resolved_sources)
resolved_sources
end
validateは「与えられたソース配列を検証する」という単一の責務に整理され、平坦化は不要になりました。build_directiveは末尾にresolved_sourcesを明示的に返すことで、戻り値がvalidateの実装詳細に依存しない形になっています。
設計判断
各メソッドの責務を明確に分離する方針が取られています。
変更前は「配列を平坦化する」という責務がvalidateに混入していました。validateは本来バリデーションのみを担うべきメソッドであり、入力の整形を行うのは呼び出し元のbuild_directiveが適切です。flat_mapへの変更はこの責務の整理を実現しています。
また、build_directiveにresolved_sourcesの明示的なreturnを追加した点も重要です。変更前はvalidateの戻り値(eachの戻り値)がbuild_directiveの戻り値として暗黙的に使われており、validateの実装が変わるとbuild_directiveの戻り値も変わってしまう脆弱な構造でした。明示的なresolved_sourcesの返却により、この依存関係が断ち切られています。
まとめ
本PRは動作を変えずに各メソッドの責務を整理したリファクタリングです。flat_mapへの置き換えと明示的な戻り値の追加という小さな変更が、「平坦化はデータを生成する側の責務」「戻り値は実装詳細に依存させない」という設計原則を明確に体現しています。