This is IT

技術、日常

【pg gem】execメソッドと、exec_paramsメソッドの違い

環境

WSL2 Debian 11.5

結論

  • exec:明示的に、SQL文の中にパラメーターを直接記述してメソッドを使う。
  • exec_params:明示的に、SQL文のパラメーターは、プレースホルダーを指定してメソッドを使う。

それぞれのコード例

execexec_paramsのそれぞれのコード例を示しておきます。

# execメソッドを使う場合
id = 1
name = "title1"

connection = PG::Connection.new(DB_URI)
result = connection.exec("UPDATE titles SET title = '#{name}' WHERE id = '#{id}'")

# exec_paramsメソッドを使う場合
id = 1
name = "title1"

connection = PG::Connection.new(DB_URI)
result = connection.exec("UPDATE titles SET title = $1 WHERE id = $2", [name, id])

execメソッドでは変数を展開しつつ、SQL文に直接値を埋め込んでいるのに反し、 exec_paramsメソッドでは、プレースホルダー内に変数を格納しておき、パラメーターは$1$2プレースホルダー内の位置情報を指定しています。

一応どちらもプレースホルダーを利用する/しないで実装をすることが出来るのですが、exec_paramsだと、SQLインジェクション対策として、自動でエスケープ処理を行ってくれるため、exec_paramsを利用することが推奨されています。

SQLインジェクションとは?

SQLインジェクション(英: SQL injection)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のことである。 Wikipedia

Wikipediaの例をもとに、SQLインジェクション攻撃の例と、exec_paramsが対策案となる理由をもう少し深堀していきましょう。 例えば、下記のようなSQLクエリがあるとします。

SELECT * from titles WHERE name = '#{name}'

そこで悪意を持った第三者が、このようなデータをフォームから送信をしたと仮定します。

name: t' OR 't'='t

この値が渡された場合、SQLクエリは次のようになります。

SELECT * from titles WHERE name = 't' OR 't'='t'

このクエリでは、本来は入力したnameに一致するレコードのみを出力したいはずが、OR 't' = 't'の条件により、必ず値がtrueになってしまうため、全てのレコードが出力されてしまいます。

exec_paramsがSQLインジェクションの対策となる理由

先ほどのexec_paramsメソッドをもう一度示します。

# exec_paramsメソッドを使う場合
id = 1
name = "title1"

connection = PG::Connection.new(DB_URI)
result = connection.exec("UPDATE titles SET title = $1 WHERE id = $2", [name, id])

クエリ文の処理を行う際、初めに解析されるのは、

UPDATE titles SET title = $1 WHERE id = $2

の部分のみになります。

その後、$1$2の位置情報をもとに、[name($1), id($2)]の値が引き渡されることとなります。

exec_paramsを利用するメリットとして、この値の引き渡しの際に、自動でエスケープ処理を行ってくれることがあります。 例えば、クォーテーションやバックスラッシュなどをエスケープをしてから、値を渡すため、先ほどのSQLインジェクションの攻撃は防ぐことが可能です。

まとめ

参考サイト

【Webを支える技術】HTTPのメッセージのどこら辺がステートレスなんだ?を自己解釈した

概要

「HTTPはステートレスなプロトコルである」

Webに触れている人なら一般的な知識で、"HTTP ステートレス"で検索をすると沢山の記事が出てきます。

自分はお恥ずかしながら、最近Webを支える技術を読んで初めて知った知識です。

という話はさておき、この本でステートレスとHTTPについて学んでる内に、実際に書籍に記述してあるHTTPメッセージの例を見て、「このメッセージのどこで自分(クライアント)の情報を伝えてるんだ・・・?」と疑問が湧いてきました。

自分なりにこの疑問を解決できたので、記事にしておきたいと思います。

各用語の簡単なおさらい

調べれば沢山分かりやすい記事が出てくると思いますが、先述の書籍、Webを支える技術からのを一部引用しつつ各用語をおさらいしておきたいと思います。

ステートレス
「サーバがクライアントのアプリケーション状態を保存しない」制約のこと。(p.32)
アプリケーション状態
アプリケーションの利用者が持つ状態のこと。たとえばソーシャルブックマークアプリの場合は、「ブックマーク一覧を表示している」「新しいブックマークを追加しようとしている」などの状態が具体例となる。(p.38)

では、実際のHTTPメッセージはどうなっている?

上記の用語や、この記事ハンバーガーの例を見てみると、HTTPメッセージを送るときは、毎回アプリケーション状態を送る必要があると自分は解釈をしています。

そこで、HTTP入門 - とほほのWWW入門のHTTPのリクエストメッセージを例に見てみたいと思います。

GET / HTTP/1.1
Accept: image/gif, image/jpeg, */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;)
Host: www.xxx.zzz
Connection: Keep-Alive

ん・・・?

これのどこに"アプリケーション状態"が書いてあるんだ・・・!?

アプリケーション状態
アプリケーションの利用者が持つ状態のこと。たとえばソーシャルブックマークアプリの場合は、「ブックマーク一覧を表示している」「新しいブックマークを追加しようとしている」などの状態が具体例となる。(p.38)


アプリケーション状態って、「今ぼくはこのページにいるよ~」とか、「ここのリンクを押そうとしているよ~」といった情報なのではないのかい?


なぜだい?そんなのどこにも書いていないじゃないか?なんなんだアプリケーション状態って!!

アプリケーション状態は、"動詞"とは限らない

今回の一番の勘違いポイントです。
Webを支える技術の例だけで、てっきりアプリケーション状態とは、"動詞"を表すものだと思っていました。

どうやらアプリケーション状態は、ユーザーのIDやパスワード、指定する自然言語など、全てをひっくるめたものを指しているようです。

上記のHTTPの例でいうと、ヘッダの部分である

Accept: image/gif, image/jpeg, */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;)
Host: www.xxx.zzz
Connection: Keep-Alive

が丸々アプリケーション状態に当てはまるのではないでしょうか。
ちゃんと必要な情報を送ってくれていますね。ただ毎回毎回送るのも面倒臭そうな気もしますが、このステートレスというアーキテクチャスタイルのおかげで、Webは発展してきました。

Cookieを使ったセッション管理

RESTに反してしまいますが、Cookieを使ったセッション管理は、ステートレスの欠点を補うためにあります。

上の説明では、『ユーザIDやパスワード』もアプリケーション状態にあると記述しました。しかし、ステートレスですと、同じサイトでページを遷移するごとに毎回IDやPWといった情報を送る必要があり、面倒くさいし、通信速度的にもあまり良い気はしません。

そこで、Cookieを用いることで、ユーザ情報、つまりはアプリケーション情報をセッションIDとして、サーバサイドで保存することができます。

気になる方は、下記のQiita記事が分かりやすかったので読んでみてください。

Cookieとセッション管理

ステートフルなプロトコルFTP

最後に、またまたWebを支える技術から、ステートフルなプロトコルFTPの説明が簡単に乗っていたので、書いておきたいと思います。

ステートフルなプロトコルの代表例はFTPです。FTPではクライアントがFTPサーバにログインしてからログアウトするまで、そのクライアントがどのディレクトリにいるかといったアプリケーション状態をサーバが管理します。そのためクライアントは、ディレクトリの移動などで相対パスを指定できます。(p.82)

確かに、HTML内のリンクでは相対パスは使えますが、HTTPメッセージでは相対パスを利用している例は見たことがありません。相対パスの様に見えても、ヘッダでHostを指定しているので、絶対パスと同義になるのかと思います。 ("HTTP 相対パス"で検索してもヒットしませんでした。)

他にも、TCPSSHがステートフルなプロトコルの代表例としてあります。

まとめ

実はまだWebアプリを作るどころか、Webについては書籍を読んだくらいなので、今後勉強を進めていく内により理解が深まってくるかと思います。

RESTやHTTPといった知識は必須なので、これからも頑張ります(KONAMI)

OptionParser.parse!を2回使ったら、オプションがなくなってしまった話

概要

破壊的メソッドは元のオブジェクトも変更してしまいます。

なので当然なのですが、同じ変数に、同じ破壊的メソッドを複数回使ってしまうと、1回目と2回目以降では実行結果が変わってしまいます。

普通の破壊的メソッドだと気を付けられるのですが、OptionsParserクラスのオブジェクトだとあまり意識できなかったので自戒のメモ

コードと実行結果

コード

require 'optparse'

def main
  p load_options
  p ARGV

  p load_options
  p ARGV
end

def load_options
  opt = OptionParser.new

  params = {}

  opt.on('-l') { |v| params[:l] = v }

  opt.parse!(ARGV)

  params
end

main

実行結果

> sample.rb -l foo
{:l=>true}
["foo"]

{}
["foo"]

バグの理由

mainメソッド内の、1回目と2回目のp load_optionsの実行結果が異なってしまっています。

そもそも、load_optionsメソッド内にある、options.parseは、

  • opt.onメソッドのブロックの実行
  • 配列ARGVからオプション値を取り除く

という仕様です。

今回はparse!という破壊的メソッドを利用しているため、mainメソッド内で、ARGVは下記のように遷移しています。

def main
# この時のARGVは、`ARGV = ["-l", "foo"]
  p load_options

# load_options内の、`parse!`メソッドにより、`ARGV = ["foo"]`に遷移
  p ARGV

# 上記で破壊的メソッドによって、この段階では`ARGV = ["foo"]`のまま!
  p load_options

  p ARGV
end

となり、今回の実行結果では、

> sample.rb -l foo
{:l=>true}
["foo"]

{}
["foo"]

のように、2回目のp load_optionsでは、ARGV =['foo']を対象に処理を行うので、「オプションは何もないよ!」といった結果になってしまいます。

まとめと解決策

僕みたいな初心者はよく言われますが、『破壊的メソッドは出来るだけ使わない』ように心がけた方が、思わぬバグを減らせるかもしれませんね。

今回はサンプルのコードなので大分簡略化しましたが、実際に実装したコードでは、一度変数に代入をすることで、破壊的メソッドを伴う処理が何度も呼ばれないように修正をしました。

# 修正前
options =
  load_options.empty? { l: true, w: true} : load_options

# 修正後
options = load_options

options = 
  options.empty? { l: true, w: true} : options

まぁ良い設計なのかはさておき、『破壊的メソッドを伴う処理は、戻り値を一度変数に入れる』というのも1つの解決策なのだと思います🥰

あとそもそも破壊的メソッド使わなければいいんじゃないか?という話ですが、そうすると今度は逆に、ARGV配列が常に["-l", "foo"]のまま変化しなくなってしまうので、諸々支障が出ます(割愛)

【PostgreSQL】ユーザ作成後、ログインが出来ない問題を解決した(Peer authentication failed)

エラー内容

[環境] Debian11.0 PostgreSQL15.1

PostgreSQLでユーザを作成後、そのユーザでログインができない!という事象が発生しました。 1日くらいハマっていたのですが、解決できたので手順を共有します。

なお、出力されたエラー文としては下記となっております。

psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  Peer authentication failed for user "ユーザ名"

結論

  • PostgreSQLで作成したユーザと同名のユーザを、Linux上でも作成しておく。
  • pg_hba.confを編集し、Peer認証の設定を解除する。

のどちらかで、今回のエラーを回避することができました。 順に説明をしていきます。

エラー発生までの手順

  1. ユーザの作成(成功)
  2. 作成したユーザで、データベース作成(成功)
  3. 作成したユーザでPostgreSQLにログインを試みる(エラー発生)

1. ユーザの作成(成功)

PostgreSQLをインストール後、ターミナルからcreateuserコマンドで、ユーザ(正しくはロール)を作成することができます。

postgresLinux上で切り替え、ユーザの作成を行います。

# postgresユーザに切り替え
❯ sudo su - postgres

# "testadmin"というユーザをパスワード付きで作成
postgres@DESKTOP-GC00DP4:~$ createuser --pwprompt --interactive testadmin
Enter password for new role:
Enter it again:
Shall the new role be a superuser? (y/n) y

引き続き、作成したユーザtestadminで、データベースの作成を行っていきます。

2. 作成したユーザで、データベース作成(成功)

createdb -O <ユーザ名> <データベース名>で、オーナーとなるユーザを指定しながらデータベースを作成することが出来ます。

postgres@DESKTOP-GC00DP4:~$ createdb -O testadmin testhoge

psql -lでデータベースの一覧を出力することで、ちゃんと作成ができているかの確認をすることができます。

postgres@DESKTOP-GC00DP4:~$ psql -l
                                             List of databases
   Name    |   Owner   | Encoding  | Collate | Ctype | ICU Locale | Locale Provider |   Access privileges
-----------+-----------+-----------+---------+-------+------------+-----------------+-----------------------
~~~~~~省略

 testhoge  | testadmin | SQL_ASCII | C       | C     |            | libc            |
(9 rows)

3. 作成したユーザでPostgreSQLにログインを試みる(エラー発生)

今回のメインです。

PostgreSQLを再起動後、psql -U <ユーザ名>で、指定したユーザでデータベースに接続が出来るはずなので、試してみたところ・・・。

# PostgreSQlを再起動
❯ sudo service postgresql restart
Restarting PostgreSQL 15 database server: main.

~
# postgresユーザに切り替え
❯ sudo su - postgres

# 作成したユーザで接続を試みる
postgres@DESKTOP-GC00DP4:~$ psql -U testadmin

psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  Peer authentication failed for user "testadmin"

データベースは作成できたのに・・・。

どういった内容のエラーなのか?

今回のエラーとなっている理由は、ここです。

Peer authentication failed for user "testadmin"

どうやらデフォルトでは、あるユーザで接続をする場合、「Peer認証」をしている模様。

peer認証方式はカーネルからクライアント上のオペレーティングシステムのユーザ名を取得し、 それをデータベースユーザ名(オプションのユーザ名マップとともに)として使用することにより動作します。この方法はローカル接続でのみ使用可能です。

つまり、クライアント(Linux,Unix)でのユーザ名と、PostgreSQLで登録したユーザ名が一致している場合のみ、データベースへの接続が可能な認証方式とのこと。

解決の手順

解決の手順として、2パターンあります。

1つ目は、単純にPeer認証の方式に則って、Linux上でも同名のユーザを作成し、そのユーザからPostgreSQLに接続をする方法。

もう一つは、そもそものPeer認証を無効にする方法です。

1つ目は言わずもがななので、2つ目の方法の説明をしていきます。

Peer認証の無効化

pg_hba.confに認証方式の設定が記述されているので、ここを編集してあげることで、Peer認証の無効化をすることができます。

自分の場合は、/etc/postgresql/バージョン数値/main/に、configファイルが格納されていました。

sudo vim pg_hba.conf等で、中を覗いてみます。

# TYPE  DATABASE        USER          ADDRESS               METHOD

# IPv4 local connections:
host    all             all           127.0.0.1/32          peer
# IPv6 local connections:
host    all             all           ::1/128               peer
# Allow replication connections from localhost, by a user with the
# replication privilege.
host    replication     all           127.0.0.1/32          peer
host    replication     all           ::1/128               peer

末カラムに記載されいるpeerが、Peer認証方式の設定となっています。

ここの記述をmd5(パスワード認証)もしくは、trust(接続を無条件で許可)に変更をすることで、Peer認証方式を解除することが可能です。

※ローカルでしか使用しないデータベースだったので、今回はtrustに設定しました。

※お恥ずかしながら、どの部分が今回の認証方式か分からなかったので、すべてのpeertrustに変更しました。

trust認証は推奨されていません。

無事解決!と思いきや・・・?

Peer認証からTrustに設定変更をしたため、これでLinux上でわざわざユーザを作成せずとも接続が出来るはずです。

psql -U <ユーザ名>で接続を試してみます。(PostgreSQLを再起動するのを忘れずに。)

❯ psql -U testadmin
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  database "testadmin" does not exist

今度は、「データベースが"testadmin"が存在しない」という旨のエラー文が出てしまいました。

どうやら、これはデータベース名を明示的に指定しない場合、ユーザ名と同名のデータベースへの接続を試みるという仕様のようです。

psql -U <ユーザ名> -d <接続先のデータベース名>で、接続先のデータベースまでしっかり指定してあげましょう。

postgres@DESKTOP-GC00DP4:~$ psql -U testadmin -d testhoge
psql (15.1 (Debian 15.1-1.pgdg110+1))
Type "help" for help.

testhoge=#

まとめ

以上、自分がはまったところの解決手順でした。 PostgreSQLのデータベースに接続をしようとした際、「Peer authentication failed」のエラー文が出てしまった場合は、

  • PostgreSQLで作成したユーザと同名のユーザを、Linux上でも作成しておく。
  • pg_hba.confを編集し、Peer認証の設定を解除する。

のどちらかを試してあげるとよいと思います。

(Ruby)optparseの基本をまとめた

はじめに

library optparse (Ruby 3.1 リファレンスマニュアル)に記載してある

require 'optparse'
opt = OptionParser.new

opt.on('-a') {|v| p v }
opt.on('-b') {|v| p v }

# parse() の場合、ARGVは変更されない。
# オプションを取り除いた結果は argv に設定される。
argv = opt.parse(ARGV)

p argv

を理解するために、optparseの基本をまとめました。

  • opt.on('-X') { ブロック処理 }
  • ARGV
  • opt.parse(ARGV) / opt.parse!(ARGV)

の3点が分かれば土台的な部分の理解はOKだと思うので、これらについて記事を書きます。

間違っている部分がありましたら是非ご指摘ください。

opt.on('-X') { ブロック処理 }を理解する

opt.on('-a') {|v| p v }
opt.on('-b') {|v| p v }

引数をオプションとして登録します。 なお、この段階ではブロックの処理は行われません

longオプションや、help時に記載される説明も、ここのメソッドで定義することが出来ますが、あくまで「オプションの登録」と「ブロックは実行されない」ことを意識していればOKだと思います。

ブロックの処理は、後述する opt.parse(ARGV) / opt.parse!(ARGV)で実行されます。

ARGVを理解する

ARGVには、スクリプト実行時に利用したオプションと、その引数を文字列要素として格納した配列が代入されています。

実際に簡単なコードを書いて確認してみたいと思います。

# opt.rb

require 'optparse'
opt = OptionParser.new

opt.on('-a') {|va| p va}
opt.on('-b') {|vb| p vb}

p ARGV
# 実行結果

❯ ruby opt.rb -a foo -b bar hoge
["-a", "foo", "-b", "bar", "hoge"]

opt.rbという名前のRubyスクリプトに、

  • オプション: -a, -b
  • 引数: (-a) foo, (-b) bar hoge

を渡して、最後にARGVを表示させる処理を書いてみたところ、このような実行結果になりました。

このことから、オプション・引数を文字列とした要素を持つ配列がARGVに代入されていることが確認できます。

opt.parse(ARGV) / opt.parse!(ARGV)を理解する

OptionParseクラスのparseメソッドは、ARGVから、オプションの要素を取り除いたものを戻り値とするメソッドとなります。 また、opt.onのブロックの実行も行います。

こちらも実行結果を確認したいと思います。

# opt.rb

require 'optparse'
opt = OptionParser.new

opt.on('-a') {|va| p va}
opt.on('-b') {|vb| p vb}

parse = opt.parse(ARGV)

p parse
# 実行結果

❯ ruby opt.rb -a foo -b bar hoge
true
true
["foo", "bar", "hoge"]

実行結果を見ると、trueという表示が2行続けて並んでいます。これが、opt.onメソッドのブロック内の処理となります。 parseメソッドを呼び出したタイミングで、ブロックの処理が行われていることが分かります。

また、戻り値を確認するためのp parseの結果を見ると、ARGVからオプションの要素のみが取り除かれた配列が表示されています。

parse!の場合

破壊的なメソッドになります。 そのため、ARGVからもオプションの要素を除外してしまいます。

# parse!に変え、ARGVを見てみる

parse = opt.parse!(ARGV)

p ARGV
# 実行結果

❯ ruby opt.rb -a foo -b bar hoge
true
true
["foo", "bar", "hoge"]

ARGVには本来、オプションも引数も両方格納されているはずですが、parse!の破壊的メソッドによってオプションが取り除かれていることが分かります。

なぜtrueという表示をするのか?

もう一度ここで、opt.onメソッドのブロックを見てみます。

opt.on('-a') {|va| p va}
opt.on('-b') {|vb| p vb}

実はブロックパラメータには、オプションを使用したか否かのBooleanオブジェクトが渡されています。 今回は、-a -b共にオプションとして指定していたため、どちらもtrueが各パラメータに渡されています。

そのため、一方のオプションを指定しないと、下記のように指定した方のtrueのみが表示されます。

❯ ruby opt.rb -a
true

結合したオプション(-ab-alrなど)の場合

# opt.rb

require 'optparse'
opt = OptionParser.new

opt.on('-a') {|va| p va}
opt.on('-b') {|vb| p vb}
opt.on('-c') {|vc| p vc}

opt.parse(ARGV)
# 実行結果

❯ ruby opt.rb -abc
true
true
true

御覧の通り、オプションを-abcと連結して渡しても、各ブロックパラメータにはtrueが渡されています。 連結しても、バラバラで書いてもしっかりとオプションは渡せるようです。

最後に

オプションの理解が浅かったため、基本的な部分をまとめました。 リファレンスだけでは把握するのは難しいため、実験的に色々自分で実行してみるのが一番良いですね。

`=`の位置は揃えないほうが実はいいかも、というお話

はじめに

プログラミング初心者による記事です。 なお、インデントや=の位置は、基本的にチームのコード規約に沿って書くのがベストです。

コードの比較

# =の位置を揃える
foo      = 'foo'
hoge     = 'hoge'
hogehoge = 'hogehoge'

# =の位置を揃えない
foo = 'foo'
hoge = 'hoge'
hogehoge = 'hogehoge'

前者の方が見やすいですよね。僕もそうなんですが、数学が好きだった人は、=を揃える方が何かと気持ち良いかもしれません。

=を揃えることによるデメリット

しかし、揃えてしまったがために、面倒くさいデメリットがあります。

それは、変数名を変えた際に再度=を揃えなければいけないことです。

# 変更前
foo      = 'foo'
hoge     = 'hoge'
hogehoge = 'hogehoge'

# 変更後
foo      = 'foo'
hoge     = 'hoge'
hogehogehogehoge = 'hogehoge'

hogehogehogehogehogehogeに変更をしたことで、=の位置がずれてしまいました。 例では3行程度しかないので良いですが、実際のコードでは直すのに大変な手間になってしまうでしょう。

もしかしたら拡張機能や、その他エディタの機能を利用して簡単に修正できる方もいらっしゃるかもしれません。 しかし、その場合でも次のようなデメリットが生じます。

レビューも面倒くさくなる

GitHubでの話となりますが、GitHubではPullRequestを出した際、前回のコミットとの差異を示してくれるFiles changedという機能があります。 もちろん人によりけりですが、基本的にここを見ないことはないと思います。

Files changedの例

ちゃんと細かい差異まで見てくれるので、「1文字消した・増やした」レベルまでも色でハイライトされて表示されてしまいます。

`,`を削除した

さぁ、ここで『変数名が変更されたから=の位置も全部揃えなおそう!』なんてやってしまうと、FilesChangedは大量の赤と緑で塗られた画面となってしまいます。 なぜなら=を揃えるために追加・削除した空白文字も、"変更点"としてGitHubは拾ってしまうからです。

変数名を1つ変えただけなのに、大量の変更点が表示されてしまったら、レビュワーとしては見づらくてたまらないですね。

さいごに

初めにも言いましたが、あくまでチームの方針に則った書き方をするのがベストです。

ちなみに僕は「リーダブルコードにそんなことが書いてあったぞ!」で意気揚々と=の位置を揃えて悦に浸っていました。 無知の頑張りは怖いものです。

【プログラム初心者必見】プロによるレビュー内容のまとめ

概要

 現在、私はフィヨルドブートキャンプというスクールでお世話になっています。 ここでメンターをしてらっしゃる、伊藤淳一さんからプロ目線でのコードレビューをして頂いたので、自分の後学のためにもまとめておきたいと思います。

 なお、実際にプラクティス中の課題で受けたレビューになりますので、内容はある程度抽象化して記載していきます。 また、プラクティスの中でも序盤の方の課題となるため、レベル的にはプログラミング未経験~初学者向けの内容になるかと思います。

  • 変数名の付け方
  • コメントの付け方
  • その他

変数名の付け方

複数形の単語で終わる変数名は、「配列」として捉えられることが多い

 複数形で終わる変数名は、「配列」として認識されることが一般的に多いようです。 右辺は単なる数値を返すメソッドにも関わらず、左辺の変数名がそのようになっている場合には気を付けましょう。

# BAD
lines = hoge.length

# GOOD
line_char_length = hoge.length


変数名の末尾の単語に意識を向ける

 上記に付随した指摘になります。

 変数名は基本的に、末尾の単語を見ることで、どのような型のオブジェクトが代入をされているのかを予測します。

max_char_finename = hoge.max_by(&:length).length

 上記の例だと、左辺の末尾の単語を見るとfilenameとなっているので、「ファイル名を代入しているのかな?」と予測されてしまいます。

 右辺のメソッド通り、ファイル名の長さを今回は代入をしたいので、

max_filename_length = hoge.max_by(&:length).length

のようにコードを書いてあげた方がより良いものとなります。

スコープが短くても変数名は適当に付けない

 スコープが短いと、arrtmpなどのいわゆる何の意味ももたない変数名を使いがちです。

 もちろん、iなどの変数名を使う場面もあるのですが、何の意味も持たない変数名は付けないほうが吉です。(伊藤さんは「無味無臭」な変数名と名付けています)

after_XXXing_YYYの変数名は、XXXed_YYYに変える

 『XXXをした後のYYY』という変数名を付けたい場合は、単純にXXXを過去形にした方が短い変数名になります。

# BAD
after_transposing_files

# GOOD
transposed_files


コメントの付け方

見ればわかるコメントを書かない

 コードを見ればわかる事をコメントに書いてしまうと、余分なノイズになってしまいます。基本的には"WHY"に答えているようなコメントを記述してあげるのが良いです。

qiita.com

主観的なコメントを避ける

 人によって見方が変わる主観的なコメントは避けましょう。人によって捉え方が変わってきてしまうので、こちらが意図したものとは違う認識をされてしまう恐れがあります。

# データが大きすぎると処理が遅くなるため、ここで一度このような処理をしました。
# 綺麗に表示をさせるため、左揃えにしました。

 例えば「データが大きすぎると」→「データが100MBを超えてしまうと」のように数値のような客観的な指標を利用してコメントをした方が誤認識を生む可能性は下がるでしょう。

 また、そもそも本当にコメントを付ける必要があるのか?という考えも大事です。「綺麗に~」の部分は主観的かつ、どのような表示をさせたいのかはコードを見れば十二分に分かるため、そもそもこのコメント自体が必要ありませんでした。

その他

mainメソッドを定義する

 これは目から鱗でした。多分今後もこのような書き方を自分はしていくと思います。ぜひ記事を読んでみてください。

qiita.com

「falseとnil以外は全て真」であることを意識する

 意外と忘れがちです。このことを意識すると、条件分岐などはもっと短く書けるかもしれません。

 例えばある配列のnilのみを除外して、表示させる処理ではこの考えを利用してリファタクリングが可能です。

foo = [1, 2, nil, 3, nil]

# BAD
foo.delete(nil)
foo.each{|int| p int} #=>1 2 3

# GOOD
foo.each do |int|
  p int if int
end


メソッドを飛び越えるためだけのインスタンス変数は避ける

 僕みたいな初心者はやりがちかもしれません。「他のメソッドでも同じ変数を使いたいからインスタンス変数にしちゃえ~!」はよくありません。

 メソッドの戻り値は最後に評価された式のみを戻り値にしてくれます。そのため、一つのメソッド内でいくつもの処理を行おうとしすぎると、使いたくなる戻り値が増え、インスタンス変数を使いたくなる問題が生じやすいです。

qiita.com

無限ループを生むリスクを避ける

 whileやuntilは無限ループを生む可能性があるので、避けるのが吉です。 timesメソッドを出来たら使いましょう。

# BAD
sum += 1 while foo

# GOOD
hoge = 数値
hoge.times { sum += 1}


破壊的メソッドを避ける

 言わずもがなです・・・。ただ「!が付いていると破壊的」なんて覚え方をしてしまっていると、罠にはまります。  例えば<<メソッドも破壊的なので気を付けましょう。

# BAD
files << nil

# GOOD
files += Array.new[]