Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

『アジャイルに効く アイデアを組織に広めるための48のパターン』を読んだ

マネージャーからおすすめされた本。でも買ったまま2年くらい積んでしまっていた。マネージャー、すみませんでした…

www.maruzen-publishing.co.jp

どういう本か?

タイトルにある通り、アイデアを組織に広めるための48のパターンについて紹介する本。

構成としては大きく3部に分かれている。

  • 第1部と第2部: パターンの使い方と体験レポート
  • 第3部: 48のパターンのリファレンス

1部は48パターンが網羅されるようにストーリー仕立てで書かれており、2部は実際にあった様々な事例についてのレポートになっている。

忙しい場合、 第1部を読むだけでも十分に価値がある。 「確かにこういうやり方ならうまくいきそうだな〜」とか、「自分がやってたこれってこういうパターン名がつくんだな」という発見がある。パターンの詳細は3部にしか書いていないが、パターンの命名がわかりやすいので雰囲気でだいたいわかる。もしわからなければ、そこだけ先に読むのも良いと思う。

誰が読むべきか?

この本はタイトルが示す読者層より、 かなり広い層がターゲットになっている。

  • アイデアを組織に広めたい、布教したい人
  • コミュニティを作る方法を知りたい人
  • 会社内で改善活動を進めようとしている人
  • 起業したい人

布教活動、改善活動などをしている人 にはぜひ読んでほしい。

あと、意外と プログラミングの設計に通ずる箇所も多かった。 「空間を演出する: 思い出すきっかけがなければ、新しいアイデアは忘れ去られてしまう」はドキュメントの管理に通ずるし、194ページの「技術を売るな、仕事上の解決を売りなさい」はプログラマが技術選定でよくやらかすミスだ。

読んでてよかった箇所、面白かった箇所

以下は読んでて面白いと思ったところなどの感想。

小さく試して、成功したらそれを祝い、失敗したら反省して次の糧にしていこう、未来は不確実でアンコントローラブルでどこに辿り着くかは誰にもわからないから、まずは小さくトライしていこうというのはその通りだなと思った。タイトルにある「アジャイルに効く」という部分がそれを表しているが、本当に大事。

成功の確率は助けを求める能力に比例しているものだ (128ページ)

この言葉、めっちゃ良い。実際そう。ひとりでタスクを抱え込んでいては進むことはない。

俺がよく唱えてる「仕事の恥はかき捨て」とだいたい同じことも書いてあってびっくりした。

あなたの最高な武器のひとつは、恥をかくのを恐れないところだ (236ページ)

改善活動をしていると、大なり小なりコミュニティを作って活動していくことになると思うが、その時に「メンバーに自分ごとと思ってもらうことが大事」って書いてあって確かにな…ととても思った。あんまりできてなかったと思う。独善的に1人で全部コントロールするんじゃなくて、ちゃんと協力してもらうの大事。

tmux.confを分割するときはtpmに気をつけよう

ハマって1時間半くらいかかったのでメモ

問題: tpmの設定を行うファイルのパス指定にHOME以外の環境変数が使えない

source-fileを使ってtmux.confを分割するのはよくあるパターンだと思います。

source-file "$HOME/dotfiles/tmux/plugins.conf"
# ~/dotfiles/tmux/plugins.conf
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'

しかし、set -g @plugin しているファイルをsource-fileするときに、パスにHOME以外の環境変数があるとtpmが動かなくなります。

# 指してるファイルは同じなのにDOTDIRの方は動作しない
source-file "$HOME/dotfiles/tmux/plugins.conf"
source-file "$DOTDIR/tmux/plugins.conf"

原因: tpm側が自力でパス解析してるから

tpmはインストール対象のプラグインを、以下のようにして集めます

  1. set -g @plugin をtmux.confから探して列挙する (コード)
  2. source-file先のファイルも展開する(コード)、ただし HOME以外の環境変数は展開しない (コード)
  3. 得られたプラグインリストを元にインストール/更新などを行う

このように、tpmはプラグイン一覧を取得するために、 tmuxを介さずに手動で静的にtmux.confを解析するという荒業 をしています。 (昔のtpm_pluginsでの指定がdeprecatedになってるのを見るに、こうしないといけない事情があったのでしょうが…)

まとめ

というわけでまとめると以下になります。

  • HOME以外の環境変数がsource-fileのパスに入っている場合、set -g @plugin はtmux.conf側で行う
  • 環境変数がHOMEのみであれば、source-file先に記述しても問題ない

tpmのコードを読まないと読み解けない仕様でそこそこ手こずりました…

テストガイドライン level 1

本ガイドラインの目的、目指すところ

  • テストの基本的な書き方、考え方を習得する
  • 原因調査時などに手動確認を減らせるテストを書けるようになる

本ガイドラインの目的外のこと: リファクタしやすくなるようにテストファイルを整備する。QAの代替を目指す。 (level2として書くかも)

テストを書く際の流れ

テストは以下の流れに沿って書く。

  1. テスト分析 (テストで確認したい事項を考え、メソッド名を決める)
  2. テスト実装 (実際のテストメソッドの中身を実装する)
  3. テスト実行 (実際にテストしてみる)

特に テスト分析を実施すること。 いきなりテストの実装から始めない。

1. 分析: テストで確認したい事項を考え、メソッド名を決める

テストの確認事項は 「手動でQAするとしたら何を調べるか?」を基準にまず考える。 そして、それを自動化できるようなソフトウェアテストを作る。

確認事項の悪い例:

  • 青森県の場合 (→その場合に期待されているアプリの挙動がが分からない)
  • method is OK (→同じく期待するアプリの挙動がわからない)
  • 返り値が正しいか? (→仕様が変化して「正しさ」の基準が変わったときに追従できない)
  • 与えられたひらがなに対応するローマ字の配列を返しているか? (→QA観点として大雑把すぎる)

確認事項の良い例:

  • 東北地方の場合送料が1000円か?
  • 選択している項目が6件未満だったら保存できないか?
  • 人数が0名の場合予約できないか? / 人数が1名以上4名以下の場合予約できるか?

悪い例は手動QAするのが難しい。 例えば「返り値が正しいか?」は、具体的にどうなれば正しいのか分からず、手動QAできない。

また、 悪い例のほうはテストがパスしても得られる情報が少ない。 たとえば「青森県の場合」というテストケースがパスしたという情報にどういう意味があるかは、テスト実装を確認しなければ分からない。

良い例のように、テストが何を確認しているのか、テストがどういう挙動を保証するのかを明確にして、情報量を増やすべき。

参考: テストコードにはテストの意図を込めよう #vstat

1つのテストケースで確認する項目は1つに絞る

1つのテストケースで確認する項目は、原則として1つとする。確認項目を絞ると様々なメリットがある(テスト実装が短くなって読みやすくなる、テストを修正しやすくなる、テストを追加するのが容易となるなど)。

例として、ひらがなをローマ字に変換する関数(transformToRoman)のテスト項目を考える。

悪いテスト項目:

  • 与えられたひらがなに対応するローマ字の配列を返しているか?

良いテスト項目:

  • うしろに"な行"か"や行"が続く場合、"ん"がnに変換されるか? 続かない場合はnnに変換されるか?
  • アルファベットが続く場合"っ"はxtuに変換されるか?
  • "っ"は後ろに続くひらがなの子音を含んで変換されるか?

悪い例は観点の粒度が大きく、複数の仕様が包含されている。 良い例では、これを細かい仕様に分割している。

良い例は粒度が細かく、 ローマ字変換の仕様についても明確に読み取ることができる。

(参考: リーダブルコード p.190)

期待値を具体的にする

期待値は「正しいか」や「問題ないか」ではなく、具体的に書く。 「正しいか」「問題ないか」は期待結果が曖昧である。

期待結果があいまいだと、異なる解釈をされる可能性があり、テストコード修正時やテスト失敗時に困る。

  • 悪い例: transformToRoman は正しい返り値を返しているか? → 関数に期待している挙動がわからない
  • 良い例: transformToRoman はアルファベットが続く場合に"っ"をxtuに変換するか? → 関数に期待する挙動が明確。

またそもそも、テストがpassする条件が揃ったときに結果として「正しい」のである。そのため、 「正しいか」という確認事項は何も言ってないのと同じである。

メソッド名について

基本的にメソッド名は、テスト項目を英訳した文章をつける。たとえば「東北地方の場合送料が1000円か?」をメソッド名にすると「calcPostage returns 1000yen for Tohoku region」となる。

describe('calcPostage()', () => {
  // 東北地方の場合送料が1000円か?
  it('returns 1000yen for Tohoku region', () => {
  });
});

このようにすることで、 メソッド名を読むだけで、コードがどういう仕様を満たせているかすぐに分かる。

2. 実装: 実際のテストメソッドの中身を実装する

テスト実装は、テスト分析の際に決めた確認したい事項を確認できるように書く。

気をつけるポイント:

  • カバレッジを上げる際、確認項目にぬけもれがないことを確認する

カバレッジを上げる際、確認項目にぬけもれがないことを確認する

たとえば以下のテストはカバレッジ100%だが重要なアルゴリズムのミスを見落としている。

// スターリンソート(前から見ていってソートされてない要素を粛清する)
const sort = arr => {
  const acc = [];
  for (const x of arr[i]) {
    if (acc.length === 0 || acc[acc.length - 1] <= x) {
      acc.push(x);
    }
  }
  return acc;
};

// 小さい順に並んでいるか?
test('sort() orders elements from smallest to highest', () => {
  const result = sort([0, 1, 3, 2, 4]);
  expect(
    result.every((val, idx) => idx === 0 || result[idx-1] < val )
  ).toBe(true);
});

// ほんとは「ソート前後で要素数が変化してないか?」も必要
// test('sort() preserves number of given array', () => {

このように、カバレッジが100%でも満たしてほしい仕様を網羅できてないことがある。 そのため、カバレッジを上げる際には気をつける (カバレッジはテストされてない領域を探すためにしか使えない!)

参考: ソフトウェアテストの7原則とは? (テストでは欠陥があることしか示せない)

3. 実行: 実際にテストしてみる

実際にテストをするのも大事だ。

たとえば、ソフトウェアが壊れているときにテストが成功しないようになっているか(false negativeになっていないか)を確認してみる。コードを壊してもテストが通ったら確認事項が漏れている。参考: Make Your Test Fail

また、テストケースの順序を入れ替えたときに破綻しないかも確認する。もしテストケースを入れ替えたときにテストがパスしなくなった場合、テスト後のクリーンアップができていない可能性が高い。テスト後に環境が元通りになるようにしておくこと。

参考

next.jsのみで簡易的な多言語対応をする

next.jsのドキュメントには多言語対応のページがあります。

nextjs.org

これを参考にすると、LPやホームページ程度であれば十分に多言語対応することができます。

next.jsだけでも出来ること

  • 文字列を翻訳する
  • アクセス時のaccept-languageヘッダーを見て自動的に言語を振り分ける (middleware.js)
  • SSGで英語版、日本語版ページそれぞれ生成する (generateStaticParams())

あとは途中にコンポーネントが挟まるパターンに対応できれば、簡易的な翻訳としては十分です。

足りないもの: 途中にコンポーネントが挟まる翻訳

途中にコンポーネントが挟まるようなコンポーネントは、このようなコンポーネントです。

// 日本語版
<span><a href="http://example.com">解説</a>を読んでください</span>
//英語版
<span>read <a href="http://example.com">guide page</a></span>

日本語と英語はたいてい語順が異なるため(上の例でもa要素の位置が異なる)、コンポーネントが挟まった翻訳は難しいです。

これを解決するために、RichText というコンポーネントを導入してやります。

function RichText({
  children,
  componentMap,
}: {
  children: string;
  componentMap: Record<string, (children: ReactNode) => JSX.Element>;
}): JSX.Element {
  const result: ReactNode[] = [];

  // [plainText, taggedText, plainText, taggedText, ...] という感じにパースして、taggedTextはコンポーネントで置き換える (["foo", <Link>link</Link> "bar"]みたいな)
  const regex = /<(\w+)>(.*?)<\/\1>/g;
  let lastIndex = 0,
    match;
  while ((match = regex.exec(children)) !== null) {
    const [fullMatch, tag, innerText] = match;
    const { index } = match;

    // plainTextを追加
    result.push(children.slice(lastIndex, index));

    // タグの中身を対応コンポーネントで包む。なければそのままテキストとして表示する
    result.push(componentMap[tag]?.(innerText) ?? fullMatch);

    lastIndex = index + fullMatch.length;
  }
  // 残りのplainTextを追加
  result.push(children.slice(lastIndex));

  return <>{result.filter((x) => typeof x !== 'string' || x.length > 0)}</>;
}

これを使うと上のコンポーネントはこう書けます

const Page = ({params}: params: Promise<{ lang: string }>}) => {
  const dict = await getDictionary((await params).lang);
  return (
    <span>
      <RichText
        componentMap={{
          link: (children) => <a href="http://example.com">{children}</a>,
        }}
      >
        {dict['<link>解説</link>を読んでください']}
      </RichText>
    </span>
  );
};

まとめ: 簡易的な対応ならライブラリなしでできる

このように、RichText さえ追加してやれば ライブラリなしでnext.jsだけでも基本的な翻訳対応をできます。 ホームページやLPくらいであれば十分です。簡易的なアプリも対応できるでしょう。

もちろん、もっと本格的に対応しようと思ったらライブラリを入れたほうがよいです(日時、通貨、複数形、数値の区切りの対応などなど)。しかし、案外next.jsだけでもできるんだなということは覚えておくと役に立つかもしれません。

最後に、本記事をもとに実際に簡易的なi18nを実装したサンプルリポジトリを置いておきます。

GitHub - pandanoir/next-i18n-simple-demo

tidy first?を読んだ

in short

  • 内容自体はとても良かったし勉強になった
  • だが、文章がおかしくて読みづらかったし、サンプルも図も不親切
  • 初心者が読んで100%理解できるのかは疑問(読まないよりは読んだほうが良いと思うが)

感想1: 読みづらかった

全体的な感想として、とても読みづらかった。 まず、(訳と原文のどちらの問題なのかわからないけど、) 言い回しが妙なところが多い。 読んでてかなりつっかえた。

あと、サンプルがどれも具体性がない。 ちょっと考えれば「あのことを言ってるのかな」とわかるが、経験の浅い初学者がこれで理解できるとは思えない。

図に関しても分かりづらい。 本文と合致してなかったり、何を意味してるのか提示されてない図が多かった。

結構多くの人がレビューに関わっていたみたいだし、前評判も良かっただけにとても残念だった。

感想2: 内容はとても良かった

読みづらくはあったが、言ってる内容自体はとても良かった。 何度も「確かに」と頷いたし、「そういうことだったんだ」と合点が言った部分もたくさんあった。

たとえば、17ページの以下。

ファイル中のコードを読み手(それぞれの書き手にたくさんの読み手がいることを忘れないように)が遭遇したいと思う順番に並べ替えよう。

基本的だけどとても大事なこと。読者目線を持つことは、プログラムだけでなく文章を書くうえでも大事なルールだ。

各部ごとの感想: 第I部は必読

第I部の整頓はどの章も重要だ。 個人的には特に15章の「冗長なコメントを削除する」ができるようになるとプログラマとしてかなりレベルが上がると思う。

ほかにも、11章の「ステートメントを小分けにする」も、ほかの書籍で言及されることは少ないけど僕もかなり大事だと思っている。プログラムコードも意味ごとに「段落」を作ると読みやすくなる。

第III部もよかった

第II部、第III部は特に読みづらい部分が多かった。でも、 第III部は大事な話も書いてあった (第II部はそんな目新しいことは書いてなかったかな)。

例えば、97ページでは、なるべく不可逆的な変更(もとに戻せない変更)を避け、可逆的な変更(もとに戻せる変更)で代替しようという話が出てくる。これは『Clean Architecture』(注:ここでは書籍名を指す)の15章に出てくる「選択肢を残しておく」とも共通している。可逆的な変更にはどんどん果敢に取り組んでいきたい。

結合、凝集のわかりやすい定義が書かれていた

101ページには結合の定義、113ページには凝集の定義が書いてある。これがとても分かりやすかった。今までふわふわしてたが、かなり腑に落ちた。

この変更の伝播特性は「結合(Coupling)」と名付けられた。1つの要素を変更するのに他の要素の変更が必要であれば、2つの要素は特定の変更に関して結合している。

(p. 101)

この結合の説明はとても明快でわかりやすい。 また、なぜ結合を減らすことがコードを読む、管理する上で大事なのかも理解しやすい。

結合した要素は同じ親要素の子要素同士であるべきだ。これが凝集(Cohesion)の1つめの意味合いだ。(中略)凝集の2つめの意味合いは、堆肥ではない要素(つまり結合していない要素)は別の場所に移すべきということだ。

(p. 113)

この凝集の説明もわかりやすい。結合した要素がバラバラに配置されているパターンはよくある(repository/FooRepo、model/FooModel みたいな)、package by layer)。これは凝集度が低い。それに対して、親が同じ状態(Foo/FooRepo、Foo/FooModel)は凝集度が高い。まとまり方が良いと言い換えても良い。親に視点を移して語るのはあまり見ないけど確かになと思った。

まとめ: 勉強にはなったけど、初学者向きかは疑問

勉強になったし考えの整理もできたので、個人的には読んで良かった。でも、文章が読みづらかったりサンプルコードや図が分かりづらいので、初学者が読んですぐ理解できるかは疑問だ。おそらく、多少経験があって行間を補完できる程度の想像力がないと効果が半減すると思う。

誰かtidy first?の行間を埋めて解説する30分くらいの発表してくれないかなぁ…それがあるととても良いなと思った。