Skip to content

最新Crystalらしい書き方へのリファクタリング#85

Closed
limit7412 wants to merge 3 commits into
masterfrom
refactor/modern-crystal-style
Closed

最新Crystalらしい書き方へのリファクタリング#85
limit7412 wants to merge 3 commits into
masterfrom
refactor/modern-crystal-style

Conversation

@limit7412

Copy link
Copy Markdown
Owner

Closes #84

最新の Crystal(1.20系)らしい書き方・設計・ツーリングへ全面リファクタリング。外部から見た挙動は不変

Phase 1: イディオム・スタイル最新化 (87bf0a8)

  • shard.yml: crystal 要件を >= 1.20.0
  • lambda.cr: puts `echo '...'` のサブシェルログ(インジェクション/クォート破壊リスク)を stdlib Log に置換。handler のブロックパラメータを明示
  • github/repository.cr: 数値比較を HTTP::Status 述語(server_error? / unauthorized? / client_error?)へ。typo (retrun/faild) 修正
  • github/models.cr: propertygetter、冗長な emit_null: false 削除、[...].includes?→定数配列+in?comment_urlpresence ||
  • error/usecase.cr: err.backtracebacktrace?.try で安全に
  • notify/usecase.cr: size != 0empty? 否定

Phase 2: 設計改善 (f86ca5f)

  • Github::NotificationsGithub::Notification(1件を表すため単数形)
  • Github::UsecaseNotificationRepository をコンストラクタ注入し、attachment 1件ごとに HTTP::Client を生成していた N+1 を解消
  • ENV 参照を main.cr(composition root)に集約し起動時に解決。各 usecase は依存をコンストラクタで受け取る
  • Slack::PostRepository@uri のみ保持、send_attachmentsend_attachments へ委譲

Phase 3: ツーリング整備 (45ca47f)

  • ameba 1.6 を development_dependency に追加し指摘を解消
  • コンパイル不能だった spec(存在しないファイルを require)を修正し、プレースホルダ(false.should eq(true))をモデルのユニットテストに置換
  • GitHub Actions CI(format / ameba / spec)を追加
  • 死んでいた .travis.yml を削除、ビルド成果物 bin/ を gitignore

検証

  • crystal tool format --check 差分なし
  • crystal build src/main.cr 成功(Crystal 1.20.2 / ローカル)
  • crystal spec … 11 examples, 0 failures
  • ./bin/ameba … 13 inspected, 0 failures

🤖 Generated with Claude Code

limit7412 and others added 3 commits June 15, 2026 14:54
- shard.yml: bump crystal requirement to >= 1.20.0
- lambda.cr: replace echo-subshell logging with stdlib Log, explicit block param
- github/repository.cr: use HTTP::Status predicate methods, fix typos
- github/models.cr: property -> getter, constant arrays + in?, presence
- usecase/error: idiomatic || and backtrace? safe navigation
- slack/models.cr: property -> getter, formatter

refs #84

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- rename Github::Notifications -> Github::Notification (singular)
- inject NotificationRepository into Github::Usecase to remove per-attachment
  HTTP::Client creation (N+1)
- centralize ENV access in main.cr (composition root); usecases receive
  dependencies via constructor
- Slack::PostRepository keeps only @uri; send_attachment delegates to
  send_attachments

refs #84

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- add ameba 1.6 as a development dependency and satisfy its lints
- fix broken spec_helper (required a non-existent file) and replace the
  placeholder spec with model unit tests (Subject#color/#update?/#comment_url,
  Notification#mention?, JSON parsing, Slack serialization)
- add GitHub Actions CI running format check / ameba / spec
- remove obsolete .travis.yml; gitignore built bin/

refs #84

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the codebase to use dependency injection, updates the Crystal version requirement, adds Ameba for linting, and introduces comprehensive unit tests for GitHub and Slack models. It also cleans up model properties to use getters, adopts more idiomatic Crystal methods (like .in? and .presence), and updates logging to use the standard Log library. Feedback is provided to ensure that errors in the alert handler do not swallow the original exception, to avoid passing full URLs directly to the HTTP client, and to optimize log chunking to reduce memory allocations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/main.cr
Comment on lines +22 to +25
rescue error
alert_repo = Slack::PostRepository.new ALERT_WEBHOOK_URL
Error::Usecase.new(alert_repo, SLACK_ID, APP_ENV).alert error
raise error

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

エラーハンドリングの rescue error ブロック内で Error::Usecase#alert を呼び出していますが、もし Slack への通知(ネットワークエラーなど)が失敗して例外が発生した場合、元の error が再スロー(raise error)されず、Slack 通知の例外に上書きされてしまいます。

これにより、本来発生していたエラーのデバッグが困難になります。alert の呼び出しを begin ... rescue で囲み、Slack 通知の失敗をログ出力にとどめることで、元のエラーが確実に再スローされるように保護することを推奨します。

  rescue error
    begin
      alert_repo = Slack::PostRepository.new ALERT_WEBHOOK_URL
      Error::Usecase.new(alert_repo, SLACK_ID, APP_ENV).alert error
    rescue alert_error
      Serverless::Lambda.print_log "Failed to send error alert to Slack: #{alert_error.message}"
    end
    raise error

Comment thread src/github/repository.cr
@@ -43,21 +43,21 @@ module Github
end

res = @github.get url

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

HTTP::Client のインスタンスメソッド(@github.get)にフルURL(https://...)を直接渡すと、リクエストラインにフルURLがそのまま送られてしまいます。これはプロキシ経由でない限りHTTP仕様的に不適切であり、サーバーによってはエラーを返す可能性があります。

URI.parse(url).request_target を使用して、パスとクエリ部分のみを渡すように改善することを推奨します。

      uri = URI.parse(url)
      res = @github.get uri.request_target

Comment thread src/runtime/lambda.cr
Comment on lines 40 to 44
def print_log(log : String)
log.split(//).each_slice(50000) do |line|
puts `echo '#{line.join.gsub(/(\r\n|\r|\n|\f)/, "")}'`
STDOUT.flush
log.gsub(/(\r\n|\r|\n|\f)/, "").each_char.each_slice(50000) do |chunk|
Log.info { chunk.join }
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

.each_char.each_slice(50000) を使用すると、50,000要素の Array(Char) がメモリ上に確保され、それを join して文字列を再構築するため、メモリ割り当て(アロケーション)とCPUのオーバーヘッドが非常に大きくなります。

文字列のインデックススライス(clean_log[offset, 50000])をループで回すことで、中間配列の生成を避け、大幅に効率化できます。

    def print_log(log : String)
      clean_log = log.gsub(/(\r\n|\r|\n|\f)/, "")
      offset = 0
      while offset < clean_log.size
        Log.info { clean_log[offset, 50000] }
        offset += 50000
      end
    end

@limit7412

Copy link
Copy Markdown
Owner Author

フェーズごとの分割 PR (#86#87#88) に置き換えるためクローズします。

@limit7412 limit7412 closed this Jun 15, 2026
@limit7412 limit7412 deleted the refactor/modern-crystal-style branch June 15, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

最新Crystalらしい書き方へのリファクタリング

1 participant