Skip to content

Conversation

@seicho
Copy link
Owner

@seicho seicho commented Jan 30, 2024

ご確認お願いします。

Copy link

@yoshitsugu yoshitsugu left a comment

Choose a reason for hiding this comment

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

コメントしました。
また、Pull Requestの名前も何をするものなのかをちゃんと説明している名前にしてください。

05.wc/wc.rb Outdated
Comment on lines 27 to 40
def file_stats
ARGV.map do |file_name|
contents = IO.readlines(file_name).join
stats = build_data(contents)
[file_name, { lines: stats[:lines], chars: stats[:chars], bytes: stats[:bytes] }]
end.to_h
end

def build_data(contents)
lines = contents.scan(/(\n|\r)/).count
chars = contents.split(/\s+/).count
bytes = contents.size
{ lines:, chars:, bytes: }
end

Choose a reason for hiding this comment

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

これでも悪くはないのですがmain -> file_stats -> build_data と main -> build_data の依存があるのが気になりますね。あるメソッドを呼び出す側はそのメソッドの抽象度で記述されているべきで、さらに中で使っているメソッドを使うのは抽象度があっていないように思います。雑に例えると、都道府県を列挙しているのにいきなり市区町村がまじってたら変ですよね。そんなイメージです。

今回の場合、標準入力からの入力も [] で1要素の配列として扱う、などとすればもうちょっと統一的に扱えるかなと思います。

05.wc/wc.rb Outdated
Comment on lines 14 to 22
formatted_stats = if ARGV.size == 1
format_file_stats(file_stats:, filter_params:)
elsif ARGV.size >= 2
file_stats_with_total = calcurate_total file_stats
format_file_stats(file_stats: file_stats_with_total, filter_params:)
else
input = readlines.join
stat = build_data(input)
format(stat:, filter_params:)

Choose a reason for hiding this comment

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

条件分岐はできるだけ局所的にするとどこが違うのかがわかりやすいです。

formatted_stats = (トータルの表示) if ARGV.size >= 2
formatted_stats += (トータル以外の表示)

としたほうが、トータルの表示のみ、ファイル数が2以上のときに必要なんだな、ということがわかりやすいかと思います。また、この方がDRYにしやすい、というメリットもあります。

05.wc/wc.rb Outdated
end

def file_stats
ARGV.map do |file_name|

Choose a reason for hiding this comment

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

ARGV に依存するのはできるだけ main などのエントリポイントとなるものだけにして、そこから呼び出されるメソッドには引数として渡したほうがよいでしょう。そうすると ARGV 以外で引数を指定するようになったときも main だけの変更で済みます。同様に、純粋なプログラミング上の計算に閉じないもの、たとえば標準入力/出力、DB操作、Webへのアクセスなどはできるだけ依存を内部的なロジックの中に作らないことがベストプラクティスになっています。

@seicho seicho changed the title first version wc command Feb 9, 2024
Copy link

@yoshitsugu yoshitsugu left a comment

Choose a reason for hiding this comment

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

コメントしました。

05.wc/wc.rb Outdated
end.max
end

__FILE__ == $PROGRAM_NAME && main

Choose a reason for hiding this comment

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

&& を条件分岐的に使うのはやめましょう。 if を使うようにしましょう。

05.wc/wc.rb Outdated
contents = [readlines.join]
source_names = [:stdin]
else
contents = ARGV.map{ |file_name| IO.readlines(file_name, nil).pop }

Choose a reason for hiding this comment

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

IO.readlines(file_name, nil).pop の部分、ややこしく見えますね。
File.read(file_name) で十分ではないでしょうか?
ファイルを一括で文字列で読み込む、というのは多くの人がやるであろう処理です。Rubyはいろいろな便利メソッドが用意されているので、そういう場合は該当するメソッドがないか、探してみてください。
https://docs.ruby-lang.org/ja/latest/class/File.html

05.wc/wc.rb Outdated
end
contents_with_source_name = source_names.zip(contents).to_h
stats = calcurate_stats(contents_with_source_name)
col_width = stats.has_key?(:stdin) ? 7 : max_col_width(stats)

Choose a reason for hiding this comment

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

この 7 は定数として名前をつけてあげるとよいです。
コード中の数値は時間が経つと何でこの値なのかのコンテキストが失われやすく、マジックナンバーと呼ばれたりします。

05.wc/wc.rb Outdated
stats.map do |name, stat|
filtered_stat_values = stat.filter { |k| filter_params.keys[0..].include?(k) }.values
formatted_stat_values = filtered_stat_values.map { |v| v.to_s.rjust(col_width) }.join(' ')
name == :stdin ? formatted_stat_values : "#{formatted_stat_values} #{name}"

Choose a reason for hiding this comment

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

ここで条件分岐が発生してしまっているのがややもったいなく思いますね。
修正する必要まではないですが、 一案として、そもそものデータ構造を { lines: (数値), words: (数値), bytes: (数値), file_name: (文字列 or nil) } としてあげれば、ここは "#{formatted_stat_values} #{name}" のみでよいかなと思いました。間の空白分出力されるのを避けるなら [formatted_stat_values, name].compact.join(' ') ですかね。

Copy link

@yoshitsugu yoshitsugu left a comment

Choose a reason for hiding this comment

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

細かいところコメントしました。

05.wc/wc.rb Outdated

def build_data(contents, file_name)
lines = contents.scan(/(\n|\r)/).count
words = contents.split(/[\s^ ]+/).count

Choose a reason for hiding this comment

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

細かいですが、



test

のように最初に空行があるファイルだとカウントがおかしくなりそうです。

05.wc/wc.rb Outdated
puts formatted_stats
end

def calcurate_stats(contents_with_filename)

Choose a reason for hiding this comment

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

calculate のtypoですかね?

Copy link

@yoshitsugu yoshitsugu left a comment

Choose a reason for hiding this comment

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

OKです!

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.

3 participants