SmartHRでプロダクトエンジニアをしている大澤と申します。この記事では、バックエンドエンジニアである自分がフロントエンドのコードをLLMに頼って実装した際の反省点について紹介します。
現在、LLMはだいぶ良い感じのコードを書いてくれるようになってきています。Claude Opus 4.6が生成したRubyのコードはあまり手直しの必要を感じません。日々驚いています。
ですが、それは自分がRubyとRuby on Railsの知識があるからです。
私はTypeScriptとReactについてあまり詳しくありません。SmartHRでは頼もしすぎるフロントエンドが強い同僚の方々に助けられてコードを書いてきました。
LLMを使えばそれっぽい.tsxを出すことはできます。しかし、フロントエンドの経験に乏しい自分が「良さそう」と思うフロントエンドのコードのレベルは低いです。プロンプトの詳細度やコンテキストの把握等もRubyのそれに比べて明確に低い自覚があります。
今回はそんな未熟な自分が出したTypeScriptとReactのコードと共に、実際に指摘してもらった問題を3つ紹介します。
やたらにStateを使いがち
管理画面の一覧ページで、フィルタ条件(セレクトボックスの選択値)を useState で管理していました。
Stateというのを便利なUI反映君ぐらいにしか考えていなかった私は、フィルタについてもLLMを用いてStateで実装しました。
const [filter, setFilter] = useState('') <Select options={filters} onChange={(e) => setFilter(e.target.value)} value={filter?.toString() ?? ''} /> <Button onClick={() => onShow(filter)}>
ですが、そのStateが変わることによってSelectフォーム以外に影響はなく、Stateで管理する必然性はありませんでした。
対応
同僚の方に教えてもらい、formで管理する形に変更して余計なStateを減らすことができました。Stateは状態を持ち込むので減らしたほうがいいですし、不要な再レンダリングも防げるので基本的には減らしたほうがいいでしょう。
<form onSubmit={(e) => { e.preventDefault() const formData = new FormData(e.currentTarget) const selectedFilter = formData.get('filter') if (selectedFilter) { onShow(selectedFilter) } }} > <Select id="filter" name="filter" options={filters} defaultValue={''} /> <Button type="submit">表示</Button> </form>
反省点
- 単純に開発の引き出しが少ない
- form要素を使おうという発想すらなかった
- 運用の複雑さやレンダリングコストを踏まえて、Stateを必要最小限に抑える意識が不足していた
ローディング状態のことを忘れがち
管理画面の一覧ページでは、カテゴリ一覧API(categories)とそのカテゴリのアイテムデータ一覧API(items)の2つを実行していました。
Pull Requestで、カテゴリ一覧(categories)自体がまだ取得できていないときのUIが丸ごと抜けていることを同僚氏から指摘してもらいました。試しにバックエンド側でレスポンスのレイテンシを上げたら見事に真っ白な画面が表示されました。
対応
画面の状態を以下の4つに整理して実装しました。
categories |
items |
表示 |
|---|---|---|
undefined |
— | LoaderBox(カテゴリ一覧ロード中) |
[] |
— | 「データはまだありません」 |
| あり | undefined |
LoaderBox(メインデータロード中) |
| あり | あり | テーブル表示 |
Storybookにもそれぞれの状態を整理して状態を追加しました。
反省点
- 状態の組み合わせを網羅して考えること
- 逐次実行が多いバックエンドに比べて、フロントエンドは非同期処理が複数同時に実行されるため、状態の組み合わせが増えやすく、また意識の外に漏れやすい
- Storybookで各状態のストーリーを作るのも大事。テストから考えよう
HTMLの構造を考えずに実装しがち
画面を一から作っていくとなると、デザイナーの方が組んでくれたUIを見つつ実装していくことになります。SmartHRでは主にFigmaを通してUIをやり取りすることが多いです。
SmartHRではSmartHR UIを通してUIをやり取りできるので、あまりCSSに詳しくなくてもSmartHR UIのコンポーネントをポンポン置いておけばFigmaを再現することはできます。
ですが、それはあくまで見た目だけの話で実際のHTMLの構造は異なります。
私は当初このようなUIを組んでいました。
<div> <Base> カテゴリー選択 </Base> <BaseColumn> アイテムテーブル </BaseColumn> </div>
しかし、実際Figmaで構成されていたのは次のような構造でした。
木構造としては同じですが、タグの配置が明確に違っていました。
<Base> <div>カテゴリー選択</div> <div>アイテムテーブル</div> </Base>
ここでの問題は、どちらも見た目はほぼ同じになってしまっていた点でした。私はFigmaの見た目を再現することで満足し、具体的なコンポーネント構成をFigmaから読み取るという行為を怠っていました。
対応
正しいタグの構造に変更しました。
反省点
- HTMLの構造を考えるという思考がなかったこと
- 見た目をコンポーネントを組み合わせて再現するのではなく、HTMLの構造を理解して実装することが重要
- アクセシビリティやパフォーマンスの観点でもHTMLの構造は重要
まとめ
ここまでを読んでいただき「なんだ、あまりにも基本的なことじゃないか」と思った方、正しいと思います。「LLMの使い方が悪い」と思った方も正しいと思います。例えばHTMLの構造なんかはFigma MCPを使えば改善できる可能性もあります。
今回この記事で書きたいのはLLMを使った実装が悪いのではなく、理想形を伝えきれていない自分を疑おうというものです。
どの問題も人間が担当すべきコンテキストを伝える行動が足りていなかった故に発生した問題だと思っています。ですが、なれない分野でLLMを使ったコードを目の前にすると「良さそう」となって批判的な目線をおろそかにしがちです。
人にレビューを頼んでデプロイするコードである以上、自分が責任を持って品質を向上するのが当たり前です。今回、自分はあまり良いサービス開発ができていなかったと反省しています。
We Are Hiring!
SmartHRは基礎的なことも考え抜いて模範的なサービスを作りたいエンジニアを募集しています!
レポジトリのコミットログを見ていただければわかるかと思いますが、一つ一つのコンポーネントを模範的なものにしようと日々意見が交わされている環境です。
模範的なサービスをつくるぞ!と思った方はぜひご応募を!