Yuki's Tech Blog

仕事で得た知見や勉強した技術を書きます。

【2023/1/8 ~ 2023/1/28】コードレビューで知ったことをざっくりまとめてみた

目次

概要

最近受けたコードレビューで知らなかったことをざっくりまとめます!

Rails

no_contentなのにレスポンスボディを返さない

ステータスがno_contentの場合、レスポンスボディは空である必要があるので、レスポンスボディにjsonを含めたりしないように気をつけましょう。Railsの場合、以下のように書くことでステータスがno_contentのレスポンスを返すことができます。

head :no_content

変数名がシリアライザー名と同じ場合、シリアライザーを指定しなくて良い

変数をrender jsonする場合、変数名(もしくはモデル名?)に基づいて、Railsは自動的にシリアライザーを探して利用してくれます。そのため、コントローラでわざわざシリアライザーを指定しなくても大丈夫です。シリアライザー内でbelogs_toなどを利用するときも同様です。

# before
# シリアライザー内のコード
belongs_to :shipping_address, serializer: ShippingAddressSerializer
# after
# シリアライザー内のコード
belongs_to :shipping_address

リアライザーの良いところは、内部の実装を変更せずにどんなデータを返すのかをシリアライザー側で調整できるところだと最近思いました。シリアライザーのおかげで、ある特定のユースケースで内部の実装を一気に変更するのを防ぐことができました。

React

真偽値の時の変数名とコンポーネントのprop名に気をつける

真偽値の変数名は多くの場合、is ~と命名します。なぜisをつけるのかと調べたところ、「if 主語に続けてis ~ を記述した際に自然な英文になるから、isが付けられるようになった」って説が自分の中でしっくりきました。

// この命名に従うとif文が非常に読みやすいです
if current_user.is_active
  pass

次にコンポーネントの真偽値のprop名です。コンポーネントのpropsで真偽値を渡す理由の多くが、表示を制御したいためです。そのような場合、prop名をis ~ にするとコンポーネントを使う人は「is ~って何?」となって、コンポーネント内部でis ~ がどのように使われているのかを確認する必要があります。Reactでは、自分自身の状態を管理するカプセル化されたコンポーネントを組み合わせて、複雑なUIを構築します。カプセル化されたコンポーネントとは、内部でどんな処理が行なわれているのか、状態を持っているのかを秘匿して、渡した値に応じて必要なUIだけを提供するようなコンポーネントです。このようなカプセル化をすることで、このコンポーネントを使う人が内部実装を理解しなくても良くなり、このコンポーネントを使う人の負担が減ります。そして、変更の影響もこのコンポーネントだけに局所化できます。is ~というprop名を使うと、コンポーネント内部の実装を見に行く必要があるので、カプセル化が正しくできていないということになります。このコンポーネントを利用する人にも負担をかけてしまいます。利用する人の負担を下げるためにも、prop名をshow ~ にすると良いでしょう。show ~ にすることで、このpropに真偽値を指定すると、 ~ が表示されるんだなということがコンポーネント内の実装を見なくても直感的に理解することができます。

<Order showPrice={true}>

同じfeaturesからインポートする際は、相対パスで書く

同じfeaturesであることを明示したいためでもありますが、絶対パスだと、相互依存の問題でインポートに失敗する可能性があるためです。ここら辺はあまりわかっていないので、次もう一度同じ問題に遭遇した時に調べます。

&で交差型を作れる

交差型とは、複数のオブジェクトの型をまとめた一つの型のことです。交差型を作ることで、オブジェクトの型を拡張することができます。交差型を作るためには、型同士を&で列挙します。型の一部を共有したい場合、交差型を使うことで以下のように書くことができます。

type BaseData = {
  id: string;
  logId: string;
};

type UpdateData = BaseData & PatchProductRequest;

type DestroyData = BaseData;

0時のDateオブジェクトを作成する

Dateオブジェクトを作成する際、複数の引数を指定することで、特定の日付のDateオブジェクトを作成できます。引数を指定しない場合、使用可能な最小の値が指定されます。そのため、あえて時間の部分で引数を指定しないことで、0時のDateオブジェクトを作成できます。

const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
console.log(today); // => 2023-01-28T00:00:00.000Z

日付のフォーマット用の関数を使って、日付の比較をしない

日付のフォーマット用の関数は、そもそも日付の表示を整えて表示することが目的です。そのフォーマット関数でフォーマットを整えてから比較するとなると、フォーマット関数の本来の目的からズレるし、そもそも一回フォーマットを挟むのが面倒です。このような場合、date-fnsというライブラリのisSameDayを利用すると、Dateオブジェクトを渡すだけで簡単に日付の比較ができます。

isSameDay(product.surveyedAt, new Date())

コンポーネントで定義した関数を親コンポーネントで使う

あまり、利用するケースは少ないですが、Propsで以下のように書くことによって、子コンポーネントで定義した関数を親コンポーネントで使うことができます。結構強引なやり方なので、他の方法がどうしてもない場合に利用しましょう。

// 子コンポーネント
type Props = {
  onSuccess: (setError: UseForm) => void;
};

const Todo: FC<Props> = ({ onSuccess }) = {
  const setError = // 省略
  const onClick = useCallback(() => {
    onSuccess(setError);
  }, [])

  return (
    // 省略
  );
};

仕様を明確に決めてから実装する

これはReactに限らず、機能がどんな仕様であるかを最初に具体的に全部決めてから実装にかかる方が後戻りがないので良いです。あと、実装方法が固まらないことも防ぐことができます。

Stateをいろんなところで更新できるようにしない

Stateをいろんなところで更新できるようした場合、どこがでStateが更新されているという視点を常に持ちながらコードを書く必要があります。これは他の開発者にとって、とても負担です。そして、どこからどんな変更が入るのかが分からなくなり、バグの温床になりやすいです。カスタムフック内でStateを定義した場合、Stateの更新処理は、カスタムフック内にできるだけ秘匿した方が良いでしょう。コンポーネント内にStateを定義した場合も同様で、そのコンポーネント内でStateの更新処理をするようにしましょう。こうすることで、Stateの更新箇所をカスタムフックやコンポーネントだけに局所化できます。

onClickにonをつけることで、callbackの発火させるタイミングとしての意味合いが強くなる

onClickやonSelectedなどに、なんでonがついているのかと調べたところ、onをつけることで、callbackの発火させるタイミングとしての意味合いが強くなるそうです。このような慣習があることによって、子コンポーネントのPropsの型情報を知っているだけで、子コンポーネントの呼び出し側は子コンポーネントの内部の実装を読まなくても、このpropに関数を渡せばこのタイミングで実行されるなということが分かります。

レビュワーが考え込むような複雑なコードを書かない

どんな値でStateが更新されているのかが分かりづらいようなコードを書かない方が良いでしょう。レビュワーがサッとわかるような分かりやすいコードを書く方がレビュワーの負担がなくなるので良いです。

利用頻度の高い関数で毎回同じ値を渡す場合、面倒なのでラッパー関数を作る

以下の場合、flash関数を呼び出すことでフラッシュメッセージを表示するのですが、第一引数に定数を渡しています。

// 関数呼び出し
flash(FlashType.SUCCESS, "商品情報を更新しました"),

利用頻度の高い関数で毎回このような定数を渡すのは面倒です。そのため、以下のようなラッパー関数を作ります。このようなラッパー関数を作ることで、関数の使い手の負担が減ります。

// 関数定義
const flashSuccess = useCallback((message: string) => flash(FlashType.SUCCESS, message), [flash]);

// 関数呼び出し
flashSuccess("商品ログ情報を更新しました"),

外部に公開する値は命名に気をつける

値を外部に公開する場合、可能な限り内部の実装に依存しない一般的な名称で露出した方がいいです。内部の実装に依存していない一般的な名称にした方が、使い手が理解しやすいです。

処理を実行するメソッドや関数は「動詞 + 名詞」 or 「動詞」にする

処理を実行する系のメソッドや関数は、「動詞 + 名詞」or 「名詞」にすると分かりやすいです。あくまでも、処理を実行する系のメソッドや関数の場合なので、その点を気をつけましょう。

何のものなのかが分からない命名をしない

コンポーネント名で、BrandLogoと命名すると、カードのブランドロゴなのか、自社のブランドに関するロゴなのか、もしくは他社なのか、分かりません。もしカードのブランドロゴの文脈で命名している場合、コンポーネント名はCardBrandLogoにした方が良いでしょう。

APIレスポンスのあるプロパティのデータ型を定義する際に、インスタンスしか受け付けないような型を定義しない

これは、フロントエンドでモデルを利用する際に気をつけるべきです。あるプロパティのデータ型をモデル内で定義する際に、クラスの型を指定すると、インスタンスしか受け付けないようになってしまいます。その結果、モデルのプロパティの型がクラスの型に引きづられてしまいます。APIのレスポンスデータを受け取るのにクラスの型を指定するのは、不適切です。この場合、OpenAPI Generatorなどで生成したAPIレスポンスの型を利用しましょう。

Bool値に変換する場合は、論理否定演算子(!)を2回使う

論理否定演算子を2回使うことで、ある値をBool値に変換できます。ある値をBool値に変換したい場合に使うと良いでしょう。

終わり

カプセル化ってすごいなと感じました。なんだかんだそういう基本的な原理原則はReactにも通じるので、知っておくのが大事だなと思いました。

参考記事

識別子(変数名や関数名など)の命名ガイドライン

React初学者が必ず押さえておきたい考え方とは?【コンポーネント指向のフロントエンド】 | in-Pocket インポケット

オブジェクト指向プログラミング、3つの概念と利点を押さえる | 日経クロステック(xTECH)

TypeScript: Handbook - Unions and Intersection Types

インターセクション型 (intersection type) | TypeScript入門『サバイバルTypeScript』

Date() コンストラクター - JavaScript | MDN

ReactのPresentationalコンポーネントに渡す関数は on から始めたい

モデルやメソッドに名前を付けるときは英語の品詞に気をつけよう - Qiita

active_model_serializers/rendering.md at v0.10.6 · rails-api/active_model_serializers · GitHub

JavaScriptの日付ライブラリdate-fnsでよく使うメソッドのまとめ|Playground発!アプリ開発会社の技術ブログ

【TS】keyof型演算子の使い方についてざっくりまとめてみた

目次

概要

keyof型演算子の使い方がパッと出なかったので、ブログにまとめようと思います。

keyof型演算子とは

keyof型演算子とは、オブジェクト型の複数のプロパティ名をリテラル型として持つ共用体型を作成する型演算子です。typeof型演算子は変数に対して使いましたが、keyof型演算子は、オブジェクトの型に対して使用するので、注意しましょう。keyof型演算子を使うことで、型を自分で定義する必要がなくなり、新しいプロパティが増えても型を変更する必要がないです。そのため、保守性が上がります。個人的には、keyofは擬似enumを作るときに使うかなという印象です。

以下の例の場合、type UserKey = "id" | "age" | "name"というリテラルの共用体型が作成されます。そのため、hpという文字列を代入しようとするとエラーが起きます。

type User = {
  id: string;
  age: number;
  name: string;
};

type UserKey = keyof User;

const key1: UserKey = "id";
const key2: UserKey = "age";
const key3: UserKey = "name";
const key4: UserKey = "hp";
// => Type '"hp"' is not assignable to type 'keyof User'.(2322)

参考記事

keyof型演算子 | TypeScript入門『サバイバルTypeScript』

TypeScript: Documentation - Keyof Type Operator

【Git】ローカルブランチの名前を変更する方法をざっくりまとめてみた

目次

概要

ローカルブランチの名前を変更する方法がパッと出なかったので、まとめます。

そもそもローカルブランチとは

ローカルブランチとは、ローカルリポジトリに存在するブランチのことです。ちなみに、ブランチとは、あるコミットを指し示すポインタのことです。

そもそもリポジトリとは

リポジトリとは、ファイルやディレクトリの状態を記録する場所です。記録された状態は、変更履歴としてリポジトリに保存されます。変更履歴を管理したいディレクトリをリポジトリの管理下に置くことで、そのディレクトリ内のファイルやディレクトリの変更履歴を記録することができます。あるディレクトリをリポジトリで管理するためには、git initを実行します。

git initを実行すると、.gitという名前の新しいサブディレクトリが指定したディレクトリ配下に作成されます。この.gitディレクトリに必要なリポジトリファイル(Gitリポジトリの雛形)が格納されます。

そもそもステージングエリアとは

ステージングエリアとは、コミットする前に、ファイルをフォーマットしたりレビューしたりすることができる中間領域です。このステージングエリアがあるおかげで、いきなり全てのファイルをセーブしてコミットを作るのではなく、どのファイルをセーブしてコミットを作るかを選択することができます。

ローカルブランチの名前を変更する方法

ローカルブランチの名前を変更するには、以下のコマンドを実行します。

git branch -m <古いブランチ名> <新しいブランチ名>

今いるブランチをリネームする場合は、新しいブランチ名のみ指定します。

git branch -m <新しいブランチ名>

参考記事

GitのHEADとは何者なのか - Qiita

Gitのステージングエリアについて理解する[Git] - Qiita

ローカルリポジトリとは - 意味をわかりやすく - IT用語辞典 e-Words

gitのローカルのブランチ名を変更したい - Qiita

履歴を管理するリポジトリ|サル先生のGit入門【プロジェクト管理ツールBacklog】

履歴を管理するリポジトリ|サル先生のGit入門【プロジェクト管理ツールBacklog】

Git - Getting a Git Repository

About - Git

技術書を読むときの批判的思考について

技術書を読むときの批判的思考について

技術書の予習と復習

この記事の「その本は胡散臭い人に書かれたと考える」って考え方すごく共感できるな。 胡散臭い人が書いた本だと考えて読むことで、批判的思考になって、書いてあることをそのまま受け取るのではなく自分で本当にあっているかを考えるようになる気がする。自分もRails勉強してた頃は、2冊くらい並行して読んで現場Railsの内容が本当にあっているのかを確認していたけど、いつの間にかやらなくなっていたの何でだろうなと感じた。おそらく、「エンジニアなら絶対読むべき」って本がめちゃめちゃあって、それを消化しなければ!って気持ちが先行しすぎてたせいで、本を読むこと自体が目的になっていたんだと思う笑。手段が目的になっちゃってるパターンなので、気をつけなければな、、

そもそもなんで「エンジニアなら絶対読むべき」って本を読むのか

あと話は変わりますが、そもそもなんで「エンジニアなら絶対読むべき」って本を読むのかというと、「つよつよになりたいから」とか「知的好奇心を満たしたいから」とか色々理由があると思います。自分は「つよつよになりたいから」って理由でいろんな名著を読んでたんですけど、もし自分と同じような理由で本を読んでいる場合、気をつけた方が良いと個人的には思います。エンジニアになっていろんな人と出会って気づいたのですが、名著を読みまくれば強強になれるのかというとそんなことはなくて、結局は仕事でコードを書くことを経験しまくるのがつよつよへの最短の近道なんだなという考え方になりました(もちろん全く読まないのも良くないが。そして、ただコードを書くのではなくて、ちゃんとレビューしてもらえる環境があるかも大事)。 そのため、名著を最初から最後まで全部読むってのは全くしなくなって、本業で知識が必要になったら、その知りたい内容が書いてある本を複数読むっていうお行儀の悪い読み方をするようになりました笑(もしくはネット記事を複数読む)。インフルエンサーのせいか分からないが「名著を読みまくって全部のせの状態にすれば強強になれるじゃん」という浅はかな考え方をしていたが、今思うと本当に危険な考え方だなと感じる。全部のせにするための果てしないレースをすることになるし(読む = 強くなるではないので、一生勉強し続けないといけない)、全部大事だと思って、何がポイントなのか何が大事なのかが分からなくなってくる。本を読むことで何か得た気になっているが、実は何も得ていないし、自分で何も考えていない(著者の思想がいつの間に自分の考えになってしまい、プロジェクトやコード、設計の状況に応じた最適解を自分で考えられなくなる)。本当に怖いなと感じる。本は著者の思想が強いな。やっぱり同じトピックを複数の本で読むのが個人的には良いと感じる。インフルエンサーが名著をおすすめしまくるの本当やめて欲しいなと感じました笑。 自分と同じように「つよつよになりたいけど、名著めっちゃ読まないとな、、つらい、、」って方の参考になれば幸いです。

事前に解決したい課題を設定して、解決するために必要な情報だけ読む

割と難しめの技術書を最初から最後まで覚えようとすると、全部大事だと感じてしまい何が重要なのか分からなくなってくる。そのせいで莫大な準備時間が必要になってしまうし何が大事か分かっていないので応用が効かない。事前に解決したい課題を設定して、解決するために必要な情報だけ読むのが頭に残るし応用が効くのかなと思う。 自分はやりがちなので、気をつけなければ、、笑

概念を知れればいいので、本に頼らなくても良いなと感じた

概念を知ったり、どのように使うのか、いつ使うのか、なんで使うと嬉しいのか、使わないとどんなことが起きるのか、が知れれば良いので、知る手段は本でもネットでも良いなと感じた。ある概念を理解することが大事であって、本を読むことを目的にしてはならんなと感じた。自分で基準を作っていけば良いなと思う。

なんでそうしないといけないのか?を自分で考えたり知ろうとしたら、なんか覚えるような気がする

なんでそうしないといけないのか?を自分で考えたり知ろうとしたら、なんか覚えるような気がする。そして自分のパターンを作ってブログにまとめると再利用しやすそうな気がする。最近免許の更新のために標識を覚え直したのだが、ただ覚えるより、「なんでそんな標識を道端にブッ刺さないといけないのか?ブッ刺した結果何が解決したのか何が嬉しいのか?」をネットで理由を見たり、自分で考えて自分なりの結論を導くことで、普通に覚えるよりすごく覚えやすい気がした。やっぱ背景を知るのは記憶するための方法として結構いい気がする(そして普通に覚えるより深い階層を知ることができる気がする)。背景というか目的というかよく分からないが背景ということにしておく。何か技術を習得するときにもこの方法は使えて、「なんでこの技術をわざわざ覚えないといけないのか?この技術を使うことで何が解決したのか、何が嬉しいのか」を知ることでスッと覚えられる気がする。そして、実際にもし使わなかった時の問題を実際に自分で検証してみると良いかも。こうやって考える方が自分で思考している気がする。

終わり

ある程度エンジニアとして働くための最低限の知識を得たら、あとはひたすら戦場にかり出て経験するのが良いと感じた。経験 > 本で勉強 な気がするので。自分にとっての本はあくまでも手段に過ぎないので。ネット記事とかにあるCTOのおすすめ本とか必要じゃないなら読む必要ないなと感じる。手段と目的を履き違えないように常に自問自答しないとなと思いました。そして、自分で批判思考した上で、自分なりの落とし所を見つけるのが良いなと感じました。

alu.jp

参考

技術書の予習と復習

【成田悠輔】皆さん勉強について誤解してます。東大に行っても勝ち組にはなれない。●●することが人生にとっても1番大事【成田悠輔 切り抜き】リハック アベプラ 日経テレ東大学 - YouTube

【祝全冠達成】AWS認定試験をラーメン食べて全部合格する話

【TS】typeof演算子とtypeof型演算子の違いについてざっくりまとめてみた

目次

概要

typeof演算子とtypeof型演算子の違いがごっちゃになってきたので、まとめようと思います。

typeof演算子とは

typeof演算子とは、JavaScriptで使う演算子です。値の型を示す文字列を返します。TypeScriptで使う場合も同様の使い方をします。

console.log(typeof true); // => "boolean" 
console.log(typeof "Hello World"); // => "string" 
console.log(typeof (() => void(1))); // => "function" 

// プリミティブ型以外の値の場合、"object"が返される
console.log(typeof [1, 2, 3]); // => "object" 
console.log(typeof {name: "yuki"}); // => "object" 

// typeof nullで"object"を返すのは、公式に認められている言語的なエラーである。互換性が維持されている
console.log(typeof null); // => "object" 

TypeScriptでtypeof演算子とif文やSwitch文等と一緒に使うと、条件に一致した時、その条件文のスコープ内で変数を一致した型として利用することができます。ちなみに、あるスコープ内での型を保証するチェックを行う式のことを型ガードと呼びます。以下の場合は、typeof x === "number"が型ガードです。

// unknown型は、型が何かわからないときに使う型
const x: unknown = 7;
// 'x' is of type 'unknown'.(18046)
// console.log(x + 4);

if (typeof x === "number") {
  console.log(x + 4); // => 11
}

typeof型演算子とは

TypeScriptのtypeof型演算子とは、変数から型を抽出する型演算子です。 TypeScriptのtypeof型演算子は、JavaScriptのtypeof演算子と同じ名前ですが、まったく別のものなので注意しましょう。typeofを型のコンテキスト(文脈)で使うと、typeof型演算子として利用できます。

let num = 1;
const user = {name: "yuki", age: 25};

type Num = typeof num;
// => type Num = number

type User = typeof user;
// => type User = {
//      name: string;
//      age: number;
//    }

参考記事

ボックス化 (boxing) | TypeScript入門『サバイバルTypeScript』

【JavaScriptの入門】データ型とtypeof演算子 | ワードプレステーマTCD

unknown型 | TypeScript入門『サバイバルTypeScript』

typeof演算子 (typeof operator) | TypeScript入門『サバイバルTypeScript』

TypeScript: Documentation - Typeof Type Operator

TypeScript: Documentation - Advanced Types

typeof - JavaScript | MDN

【Ruby】nilとは何かについてざっくりまとめてみた

目次

概要

nilとは何かの問いにパッと答えられなかったので、まとめようと思います。

nilとは何か

nilとは、「何もない」ことを表すオブジェクトです。nil自体もオブジェクトであり、NilClass クラスの唯一のインスタンスです。「期待したものが何もなかった」ことを表現したい場合に使用します。

TSのnullとの違い

TSのnullは、「オブジェクトの値が無い」ことを表すプリミティブ値です。プリミティブ値とは、厳密にいうとプリミティブ型の値のことで、オブジェクトではない、メソッドを持たないデータのことです。また、プリミティブ型の値は、イミュータブル(immutable)な特性を持ちます。TSのnullもRubynilも、どちらも期待した値が存在しない場合に返す用途であることが分かります。

参考記事

class NilClass (Ruby 3.2 リファレンスマニュアル)

データ型とリテラル · JavaScript Primer #jsprimer

NaN - JavaScript | MDN

null - JavaScript | MDN

Ruby の nil は無か? - Qiita

Primitive (プリミティブ) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN

【Ruby】【TS】matchメソッドについてざっくりまとめてみた

目次

概要

業務でmatchメソッドを使う時があったのですが、パッと出なかったのでブログにまとめようと思います。

matchメソッド

Ruby

Rubyにおけるmatchメソッドは、String#matchです。実引数で指定した正規表現にマッチした場合、MatchDataクラスのインスタンスを返します(MatchDataクラスは、正規表現のマッチに関する情報を扱うためのクラスです)。マッチしなかった場合はnilを返します。MatchDataクラスのインスタンスに対してMatchData#to_sを実行すると、マッチした文字列全体を返します。

regex_pattern = /^\d{2}/

target = "123xyx"
second_target = "xyz"
p target.match(regex_pattern) # => #<MatchData "12">
p target.match(regex_pattern).class # => MatchData
p target.match(regex_pattern).to_s # => "12"
p second_target.match(regex_pattern) # => nil

TS

TSにおけるmatchメソッドは、String.prototype.match()です。引数には正規表現を指定します。gフラグをつけたグローバルサーチの場合、正規表現全体に一致したすべての結果を配列で返しますが、キャプチャグループは返しません。gフラグをつけない場合、最初に一致したものとそれに関するキャプチャグループを含んだ配列を返します。一致するものが見つからなかった場合、nullを返します。

■gフラグあり

const regexPattern = /\d{2}/g;
const target = "123xyx";
const secondTarget = "xyx";

console.log(target.match(regexPattern)); // => [ '12' ]
console.log(secondTarget.match(regexPattern)) // => null

■gフラグなし

const regexPattern = /\d{2}/;
const target = "123xyx";

console.log(target.match(regexPattern)); // => [ '12', index: 0, input: '123xyx', groups: undefined ]
console.log(secondTarget.match(regexPattern)) // => null

終わり

Rubyって正規表現で繰り返しマッチ(グローバルサーチ)できたっけと悩みました、、調べても結局できたのか分からないので、Rubyガッツリ学ぶときに調べてみます。

参考

String#match (Ruby 3.2 リファレンスマニュアル)

class MatchData (Ruby 3.2 リファレンスマニュアル)

【JavaScript】正規表現オプションの意味と使い方まとめ | PisukeCode - Web開発まとめ

String.prototype.match() - JavaScript | MDN

Regex Hunting