Rubyの文字列操作が重要な理由
多くのRubyプロジェクトで必要不可欠なスキル
Rubyにおける文字列操作は、ほぼすべてのプロジェクトで必要となる基礎的かつ重要なスキルです。その理由として、以下のような実務での活用シーンが挙げられます:
- データ処理と変換
- CSVやJSONなどの構造化データの解析
- APIレスポンスの加工
- ユーザー入力の検証と整形
- テキスト生成
- レポートやログの出力
- HTMLテンプレートの生成
- 動的なSQL文の構築
- ファイル操作
- ログファイルの解析
- 設定ファイルの読み込みと解析
- テキストファイルの加工
例えば、以下のようなコードは実務でよく目にする文字列処理の例です:
# ユーザー入力の検証と整形 def sanitize_user_input(input) input.strip.downcase.gsub(/[^a-z0-9\s]/, '') end # CSVデータの処理 def process_csv_line(line) fields = line.split(',').map(&:strip) { name: fields[0], email: fields[1].downcase, age: fields[2].to_i } end
効率的な文字列処理がパフォーマンスを左右する
文字列操作は頻繁に行われる処理であるため、その実装方法はアプリケーション全体のパフォーマンスに大きな影響を与えます:
- メモリ使用量への影響
- 文字列の不適切な処理は、メモリの過剰消費を引き起こす可能性があります
- 特に大量のデータを扱う場合、効率的な文字列処理が重要になります
# メモリ効率の悪い実装 def inefficient_concat(strings) result = "" strings.each { |s| result += s } # 毎回新しい文字列オブジェクトを生成 result end # メモリ効率の良い実装 def efficient_concat(strings) strings.join # 一度の操作で文字列を結合 end
- 処理速度への影響
- 適切な文字列操作メソッドの選択が処理速度を大きく左右します
- 特に正規表現やパターンマッチングの使用方法は重要です
# パフォーマンスの悪い実装 def slow_check(text, words) words.all? { |word| text.include?(word) } # 毎回文字列全体をスキャン end # パフォーマンスの良い実装 def fast_check(text, words) pattern = /#{words.join('|')}/i # 一度の正規表現で複数のワードをチェック text.match?(pattern) end
文字列操作の重要性は、単なる機能実装の観点だけでなく、アプリケーションの品質全体に関わる重要な要素となっています。効率的な文字列処理の実装は、以下のような利点をもたらします:
- アプリケーションの応答性向上
- サーバーリソースの効率的な利用
- 保守性の高いコードの実現
- スケーラビリティの確保
このため、Rubyエンジニアには文字列操作の基礎から応用まで、体系的な理解と実装スキルが求められています。
文字列の基本概念と作成方法
シングルクォートとダブルクォートの使い分け
Rubyでは、文字列を作成する際に主にシングルクォート(’)とダブルクォート(”)の2種類の方法があります。それぞれには特徴があり、用途に応じて適切に使い分けることが重要です。
# シングルクォート str1 = 'Hello, Ruby' # 基本的な文字列 str2 = 'It\'s Ruby' # エスケープが必要 str3 = 'Line 1\nLine 2' # \n はそのまま文字として扱われる # ダブルクォート str4 = "Hello, Ruby" # 基本的な文字列 str5 = "It's Ruby" # エスケープ不要 str6 = "Line 1\nLine 2" # \n は改行として解釈される name = "Alice" str7 = "Hello, #{name}" # 式展開が可能
使い分けのポイント:
特徴 | シングルクォート | ダブルクォート |
---|---|---|
式展開 | 不可 | 可能 |
エスケープシーケンス | 最小限 | 全て対応 |
パフォーマンス | やや高速 | やや低速 |
使用推奨シーン | 単純な文字列 式展開不要な場合 | 式展開が必要な場合 エスケープシーケンスを使用する場合 |
ヒアドキュメントを使った複数行文字列の作成
複数行にわたる文字列を扱う場合、ヒアドキュメント(heredoc)を使用すると可読性の高いコードを書くことができます。
# 基本的なヒアドキュメント message = <<TEXT これは複数行の テキストメッセージです。 インデントも保持されます。 TEXT # インデント付きヒアドキュメント def send_email body = <<~EMAIL 親愛なるユーザー様 ご利用ありがとうございます。 このメールは自動送信されています。 敬具 システム管理者 EMAIL # 処理の続き... end # 式展開とエスケープを制御 sql = <<-'SQL' SELECT * FROM users WHERE name = '#{name}' -- この#{name}は式展開されない SQL html = <<~"HTML" <div> Hello, #{user.name}! -- この#{user.name}は式展開される </div> HTML
文字列のエンコーディング設定と確認方法
Rubyでは文字列のエンコーディングを適切に扱うことが、特に日本語を含むマルチバイト文字を処理する際に重要です。
# ファイルエンコーディングの指定 # coding: utf-8 # 文字列のエンコーディングを確認 str = "こんにちは" puts str.encoding #=> #<Encoding:UTF-8> # エンコーディングの変換 utf8_str = "Ruby" sjis_str = utf8_str.encode("Shift_JIS") puts sjis_str.encoding #=> #<Encoding:Shift_JIS> # エンコーディング指定で文字列作成 str_utf8 = "日本語".force_encoding("UTF-8") str_sjis = "日本語".force_encoding("Shift_JIS") # エンコーディングエラーの対処 begin invalid_str = "こんにちは".encode("ISO-8859-1") rescue Encoding::UndefinedConversionError => e puts "変換できない文字が含まれています: #{e.message}" end # 文字列結合時のエンコーディング自動変換 utf8_str = "Hello" sjis_str = "こんにちは".encode("Shift_JIS") combined = utf8_str + sjis_str # SJISがUTF-8に自動変換される
エンコーディング処理の重要なポイント:
- デフォルトエンコーディング
- Ruby 2.0以降はUTF-8がデフォルト
- ソースコードのエンコーディングはマジックコメントで指定可能
- エンコーディング変換のベストプラクティス
- 入力時に適切なエンコーディングに変換
- アプリケーション内部ではUTF-8で統一
- 出力時に必要なエンコーディングに変換
- エラー処理
force_encoding
とencode
の使い分けを理解- 適切な例外処理の実装
- 変換できない文字の代替処理の検討
このように、Rubyの文字列は様々な方法で作成・操作できますが、用途に応じて適切な方法を選択することが、可読性の高い効率的なコードの作成につながります。
基本的な文字列操作テクニック
文字列の結合と分割を効率的に行う
文字列の結合と分割は最も頻繁に行われる操作の一つです。Rubyには複数の方法が用意されており、状況に応じて最適な方法を選択することが重要です。
文字列の結合
# 1. + 演算子による結合 name = "Ruby" + " " + "Programming" # => "Ruby Programming" # 2. << 演算子による結合(破壊的メソッド) message = "Hello" message << " " << "World" # => "Hello World" # 3. concat メソッドによる結合(破壊的メソッド) greeting = "Good" greeting.concat(" ").concat("Morning") # => "Good Morning" # 4. join メソッドによる配列要素の結合 words = ["Ruby", "is", "fun"] words.join(" ") # => "Ruby is fun" # 5. Array#* による繰り返し結合 "Ruby" * 3 # => "RubyRubyRuby" # パフォーマンス比較例 require 'benchmark' strings = Array.new(10000) { "string" } Benchmark.bm do |x| x.report("+:") { strings.reduce { |result, str| result + str } } x.report("<<:") { strings.reduce { |result, str| result << str } } x.report("join:") { strings.join } end
文字列の分割
# 1. split メソッドによる分割 text = "Ruby,Python,JavaScript" languages = text.split(",") # => ["Ruby", "Python", "JavaScript"] # 2. 正規表現による複雑な分割 text = "Ruby;Python,JavaScript|PHP" languages = text.split(/[;,|]/) # => ["Ruby", "Python", "JavaScript", "PHP"] # 3. 文字数による分割 text = "Ruby programming" chunks = text.scan(/.{1,5}/) # => ["Ruby ", "progr", "ammin", "g"] # 4. 行による分割 text = "Line 1\nLine 2\nLine 3" lines = text.lines # => ["Line 1\n", "Line 2\n", "Line 3"]
部分文字列の抽出と置換を使いこなす
文字列から特定の部分を抽出したり、置換したりする操作も頻繁に必要となります。
# 1. 部分文字列の抽出 text = "Ruby Programming" text[0, 4] # => "Ruby" # インデックスと長さを指定 text[5..-1] # => "Programming" # 範囲を指定 text[-11..-1] # => "Programming" # 末尾からの範囲指定 # 2. 正規表現による抽出 text = "Email: example@ruby.org" text[/\w+@\w+\.\w+/] # => "example@ruby.org" # 3. 文字列置換 text = "I like Python" text.sub("Python", "Ruby") # => "I like Ruby" # 最初の一致のみ置換 text.gsub("Python", "Ruby") # => "I like Ruby" # すべての一致を置換 # 4. 複数のパターンを一括置換 text = "I like Python and Python is cool" replacements = {"Python" => "Ruby", "cool" => "awesome"} text.gsub(/Python|cool/, replacements) # => "I like Ruby and Ruby is awesome" # 5. ブロックを使用した動的な置換 text = "price: 100, tax: 10" text.gsub(/\d+/) { |match| match.to_i * 2 } # => "price: 200, tax: 20"
大文字小文字の変換とケース処理
文字列の大文字小文字を変換する操作は、特にユーザー入力の正規化やフォーマット統一に重要です。
# 1. 基本的な大文字小文字変換 text = "Ruby Programming" text.upcase # => "RUBY PROGRAMMING" text.downcase # => "ruby programming" text.swapcase # => "rUBY pROGRAMMING" text.capitalize # => "Ruby programming" # 2. 破壊的メソッドバージョン text = "Ruby Programming" text.upcase! # 文字列自体を変更 text.downcase! # 文字列自体を変更 # 3. タイトルケース変換(独自実装) def titleize(text) text.split(/\s+/).map(&:capitalize).join(" ") end text = "ruby on rails programming" titleize(text) # => "Ruby On Rails Programming" # 4. 特殊なケース変換 class String def to_snake_case self.gsub(/::/, '/') .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') .gsub(/([a-z\d])([A-Z])/,'\1_\2') .tr("-", "_") .downcase end def to_camel_case self.split('_').map(&:capitalize).join end end "ruby_programming".to_camel_case # => "RubyProgramming" "RubyProgramming".to_snake_case # => "ruby_programming"
これらの基本的な文字列操作を理解し、適切に使用することで、より効率的で保守性の高いコードを書くことができます。特に以下の点に注意して使用しましょう:
- パフォーマンスの考慮
- 大量の文字列結合には
join
を使用 - 破壊的メソッドと非破壊的メソッドの使い分け
- 正規表現の過度な使用を避ける
- メモリ使用の最適化
- 大きな文字列を扱う際は破壊的メソッドを検討
- 不要な中間文字列の生成を避ける
- 可読性の維持
- 複雑な正規表現はコメントで説明を追加
- 意図が明確になる命名を心がける
- 汎用的な処理は独自メソッドとして切り出す
正規表現を使った高度な文字列処理
パターンマッチングの基本と応用
Rubyの正規表現は、文字列のパターンマッチングにおいて強力なツールを提供します。基本的な使い方から実践的な応用まで、段階的に見ていきましょう。
基本的なパターンマッチング
# 1. 正規表現オブジェクトの作成 pattern1 = /ruby/i # 大文字小文字を区別しない pattern2 = %r{ruby}i # 別の記法 pattern3 = Regexp.new("ruby", Regexp::IGNORECASE) # 2. マッチングの基本操作 text = "I love Ruby programming" # マッチの確認 text.match?(/ruby/i) # => true (高速、Ruby 2.4以降) text =~ /ruby/i # => 7 (マッチした位置) text.match(/ruby/i) # => #<MatchData "Ruby"> # 否定マッチ text !~ /python/i # => true # 3. よく使用するパターン例 email = "user@example.com" phone = "090-1234-5678" date = "2024-03-15" email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i # メールアドレス phone =~ /\A\d{2,4}-\d{2,4}-\d{4}\z/ # 電話番号 date =~ /\A\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\z/ # 日付
高度なパターンマッチング
# 1. 先読み・後読み text = "price: $100, cost: $50" # 肯定先読み text.scan(/\$\d+(?=,)/) # => ["$100"] # カンマの前の金額 # 否定先読み text.scan(/\$\d+(?!,)/) # => ["$50"] # カンマが続かない金額 # 2. 条件分岐パターン def validate_password(password) pattern = /\A (?=.*[A-Z]) # 少なくとも1つの大文字 (?=.*[a-z]) # 少なくとも1つの小文字 (?=.*\d) # 少なくとも1つの数字 (?=.*[!@#$%^&*]) # 少なくとも1つの特殊文字 .{8,} # 最低8文字 \z/x pattern.match?(password) end # 3. 名前付きキャプチャの活用 log = '2024-03-15 10:30:45 [ERROR] Failed to connect: timeout' pattern = /\A (?<date>\d{4}-\d{2}-\d{2})\s (?<time>\d{2}:\d{2}:\d{2})\s \[(?<level>\w+)\]\s (?<message>.+) \z/x if match = log.match(pattern) puts "Date: #{match[:date]}" puts "Time: #{match[:time]}" puts "Level: #{match[:level]}" puts "Message: #{match[:message]}" end
キャプチャグループを使った柔軟な文字列抽出
キャプチャグループを使用することで、パターンマッチングの結果から必要な部分を効率的に抽出できます。
# 1. 基本的なキャプチャグループ text = "Name: John Smith, Age: 30" pattern = /Name: ([\w\s]+), Age: (\d+)/ match = text.match(pattern) name = match[1] # => "John Smith" age = match[2] # => "30" # 2. 名前付きキャプチャグループ pattern = /Name: (?<name>[\w\s]+), Age: (?<age>\d+)/ match = text.match(pattern) name = match[:name] # => "John Smith" age = match[:age] # => "30" # 3. キャプチャグループの入れ子 html = '<div class="container"><p>Hello, world!</p></div>' pattern = /<(\w+)\s*(?:class="([^"]*)")?>(?:<(\w+)>(.+?)<\/\3>)<\/\1>/ if match = html.match(pattern) outer_tag = match[1] # => "div" class_name = match[2] # => "container" inner_tag = match[3] # => "p" content = match[4] # => "Hello, world!" end
文字列の検証と整形における正規表現活用法
実践的なシーンでの正規表現の活用例を見ていきましょう。
class StringValidator EMAIL_PATTERN = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i PHONE_PATTERN = /\A\d{2,4}-\d{2,4}-\d{4}\z/ URL_PATTERN = /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\z/ def self.validate_email(email) email.match?(EMAIL_PATTERN) end def self.validate_phone(phone) phone.match?(PHONE_PATTERN) end def self.validate_url(url) url.match?(URL_PATTERN) end def self.sanitize_filename(filename) filename.gsub(/[^0-9A-Za-z.\-]/, '_') end def self.extract_urls(text) text.scan(/https?:\/\/[\w\-\.]+\.\w+(?:\/[\w\-\.\/\?%&=]*)?/) end end # 使用例 validator = StringValidator validator.validate_email("user@example.com") # => true validator.validate_phone("090-1234-5678") # => true validator.sanitize_filename("my file.txt") # => "my_file.txt"
正規表現を使用する際の重要なポイント:
- パフォーマンスへの配慮
- 過度に複雑な正規表現を避ける
- 可能な場合は
match?
メソッドを使用 - 大きな文字列に対する貪欲な量指定子の使用に注意
- 可読性の維持
- 複雑な正規表現はxオプションで整形
- パターンの意図を明確にするコメントを追加
- 名前付きキャプチャグループの活用
- 保守性の向上
- 頻繁に使用するパターンは定数化
- 複雑なバリデーションはクラスにカプセル化
- テストケースの作成を忘れずに
パフォーマンスを考慮した文字列処理
frozen_string_literalの適切な使用方法
frozen_string_literalは、文字列オブジェクトを不変(イミュータブル)にすることでメモリ使用量を削減し、パフォーマンスを向上させる機能です。
# frozen_string_literal: true # 基本的な使用方法 str1 = "hello" # この文字列リテラルは凍結される str2 = +"hello" # 凍結を解除(ミュータブルな文字列を作成) # パフォーマンス比較 require 'benchmark' def with_frozen # frozen_string_literal: true が有効な場合 1000.times { "hello" } end def without_frozen # frozen_string_literal: true が無効な場合 1000.times { +"hello" } end Benchmark.bm do |x| x.report("frozen: ") { with_frozen } x.report("unfrozen: ") { without_frozen } end # 文字列の結合時の注意点 def concat_strings result = +"" # 結合用の文字列は凍結を解除 ["a", "b", "c"].each do |str| result << str # 破壊的な結合が可能 end result end
メモリ効率を考慮した文字列操作の実装
メモリ効率の良い文字列処理を実現するためには、適切なメソッドの選択と実装方法の工夫が重要です。
# 1. 文字列結合の効率化 def inefficient_concat(strings) result = "" strings.each { |s| result += s } # 毎回新しい文字列オブジェクトを生成 result end def efficient_concat(strings) strings.join # 一度の操作で結合 end def buffer_concat(strings) result = String.new(capacity: strings.sum(&:length)) # バッファサイズを予約 strings.each { |s| result << s } result end # ベンチマーク require 'benchmark' strings = Array.new(10000) { "string" } Benchmark.bm do |x| x.report("inefficient: ") { inefficient_concat(strings) } x.report("efficient: ") { efficient_concat(strings) } x.report("buffer: ") { buffer_concat(strings) } end # 2. メモリ使用量の最適化 class StringProcessor def initialize(large_text) @text = large_text end # メモリ効率の悪い実装 def process_inefficient lines = @text.split("\n") processed = lines.map { |line| line.strip.downcase } processed.join("\n") end # メモリ効率の良い実装 def process_efficient result = String.new(capacity: @text.length) @text.each_line do |line| result << line.strip.downcase << "\n" end result.chomp! end end
大量の文字列処理を行う際の最適化テクニック
大規模なテキストデータを処理する際は、以下のような最適化テクニックが有効です。
class LargeTextProcessor # 1. チャンク処理による最適化 def process_by_chunks(file_path, chunk_size = 1024 * 1024) File.open(file_path, 'r') do |file| while chunk = file.read(chunk_size) process_chunk(chunk) end end end # 2. ストリーム処理による最適化 def process_stream(file_path) File.open(file_path, 'r') do |file| file.each_line do |line| process_line(line) end end end # 3. 並列処理による最適化 require 'parallel' def parallel_process(file_path, num_threads = 4) lines = File.readlines(file_path) chunks = lines.each_slice(lines.size / num_threads).to_a Parallel.map(chunks) do |chunk| process_chunk(chunk.join) end.join end private def process_chunk(chunk) # チャンク処理のロジック end def process_line(line) # 行処理のロジック end end # 実装例:大規模なログファイル処理 class LogProcessor def initialize(log_path) @log_path = log_path end def analyze_logs pattern = /\[(ERROR|WARN|INFO)\]\s+(.+)/ stats = { error: 0, warn: 0, info: 0 } File.open(@log_path, 'r') do |file| file.each_line do |line| if match = pattern.match(line) level = match[1].downcase.to_sym stats[level] += 1 end end end stats end end
パフォーマンス最適化のベストプラクティス:
- メモリ使用量の最適化
- 適切なバッファサイズの設定
- 不要なオブジェクトの生成を避ける
- チャンク処理の活用
- 処理速度の最適化
- frozen_string_literalの活用
- 効率的な文字列結合メソッドの選択
- 並列処理の検討
- コードの保守性との両立
- パフォーマンス要件の明確化
- ベンチマークによる効果測定
- 適切なコメントとドキュメンテーション
実践的なユースケースと解決方法
CSVデータの効率的な処理方法
CSVデータの処理は業務システムでよく必要となる操作です。大規模なCSVファイルを効率的に処理する方法を見ていきましょう。
require 'csv' require 'stringio' class CSVProcessor def initialize(file_path) @file_path = file_path end # 大規模CSVの効率的な読み込み def process_large_csv CSV.foreach(@file_path, headers: true) do |row| yield row if block_given? end end # メモリ効率の良いCSV生成 def generate_csv(data) CSV.generate(String.new, headers: true) do |csv| csv << data.first.keys # ヘッダー行 data.each { |row| csv << row.values } end end # CSVデータのバリデーションと整形 def validate_and_clean_csv valid_rows = [] invalid_rows = [] CSV.foreach(@file_path, headers: true) do |row| cleaned_row = clean_row(row) if valid_row?(cleaned_row) valid_rows << cleaned_row else invalid_rows << row end end[valid_rows, invalid_rows]
end private def clean_row(row) row.to_h.transform_values do |value| value.to_s.strip.gsub(/[[:space:]]+/, ‘ ‘) end end def valid_row?(row) row.values.none?(&:empty?) end end # 使用例 processor = CSVProcessor.new(‘data.csv’) # 大規模CSVの処理 processor.process_large_csv do |row| # 各行の処理 puts row[‘name’] end
日本語文字列特有の処理への対応
日本語文字列を扱う際は、文字コードや文字種の違いに注意が必要です。
class JapaneseTextProcessor KANA_CONVERSION = { 'あ' => 'ア', 'い' => 'イ', 'う' => 'ウ', # ... 他の文字の変換マップ }.freeze def initialize(text) @text = text end # ひらがなをカタカナに変換 def to_katakana @text.tr('ぁ-ん', 'ァ-ン') end # カタカナをひらがなに変換 def to_hiragana @text.tr('ァ-ン', 'ぁ-ん') end # 半角カナを全角カナに変換 def normalize_width @text.unicode_normalize(:nfkc) end # 文字種の判定 def character_types types = { hiragana: 0, katakana: 0, kanji: 0, ascii: 0, other: 0 } @text.each_char do |char| case char when /\p{Hiragana}/ types[:hiragana] += 1 when /\p{Katakana}/ types[:katakana] += 1 when /\p{Han}/ types[:kanji] += 1 when /\p{ASCII}/ types[:ascii] += 1 else types[:other] += 1 end end types end # 文字列の長さ(マルチバイト対応) def text_length @text.length end def display_width @text.each_char.sum { |c| c.ascii_only? ? 1 : 2 } end end # 実践的な使用例 processor = JapaneseTextProcessor.new("こんにちは world 123") puts processor.character_types puts processor.display_width
HTMLやXMLの文字列処理テクニック
HTMLやXMLの処理では、適切なパースと生成が重要です。
require 'nokogiri' class HTMLProcessor def initialize(html) @html = html @doc = Nokogiri::HTML(@html) end # 安全なHTML生成 def sanitize_html allowed_tags = %w[p a b i strong em] allowed_attributes = %w[href title] @doc.css('*').each do |node| unless allowed_tags.include?(node.name) node.replace(node.text) end node.attributes.each do |attr_name, attr| unless allowed_attributes.include?(attr_name) attr.remove end end end @doc.to_html end # メタタグの抽出 def extract_meta_tags meta_tags = {} @doc.css('meta').each do |meta| name = meta['name'] || meta['property'] meta_tags[name] = meta['content'] if name end meta_tags end # リンクの抽出と検証 def validate_links links = [] @doc.css('a').each do |link| href = link['href'] next unless href links << { url: href, text: link.text, valid: valid_url?(href) } end links end private def valid_url?(url) uri = URI.parse(url) uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) rescue URI::InvalidURIError false end end # テンプレートエンジンの実装例 class SimpleTemplate def initialize(template) @template = template end def render(variables = {}) result = @template.dup variables.each do |key, value| result.gsub!(/\{\{\s*#{key}\s*\}\}/, value.to_s) end result end end # 使用例 template = SimpleTemplate.new(<<-TEMPLATE) <div class="user-profile"> <h1>{{name}}</h1> <p>Email: {{email}}</p> <p>Age: {{age}}</p> </div> TEMPLATE variables = { name: '山田太郎', email: 'yamada@example.com', age: 30 } puts template.render(variables)
実践的な文字列処理を行う際の重要なポイント:
- エラー処理の考慮
- 不正なデータへの対応
- 文字コードの適切な処理
- 例外処理の実装
- パフォーマンスとメモリ使用
- 大規模データの効率的な処理
- ストリーム処理の活用
- 適切なバッファリング
- セキュリティへの配慮
- 入力データのサニタイズ
- XSS対策の実装
- 適切なエスケープ処理
- 保守性と再利用性
- モジュール化された設計
- 適切な抽象化
- テストの作成