リーダブルなコードを書く習慣の身に付け方・実践の仕方 - 2021-09-22 - ククログ

ククログ

株式会社クリアコード > ククログ > リーダブルなコードを書く習慣の身に付け方・実践の仕方

リーダブルなコードを書く習慣の身に付け方・実践の仕方

結城です。

2021年9月13日から14日にかけて、東京都立大学の大学院生向け特別講義として「リーダブルコード演習」を実施しました。

演習の内容は、当社でこれまでにも行ってきているリーダブルコードワークショップを、プログラミング経験が比較的浅い・プログラミングの量がまだそれほど多くない方向けに調整した内容としました。 この記事では、実施した演習の概要と、今回意識した点を紹介します。

本文が長いため、目次を用意してみました。

発端

この度の講義は、同大学准教授の小町さんからご相談を頂いた事がきっかけで実現しました。

小町さんは以前から、学生の方々の大学院卒業後のことも見据えた授業を行っておられて、これまでにも、企業で働くITエンジニアの方々を講師として招き、集中講義の形でAWSやDocker、データ分析のフレームワークなどに実際に触れる授業をされているそうです。 本ブログでこれまでに公開してきた記事を小町さんがご覧頂いていたことから、集中講義の枠の1つとして、当社エンジニアが持つ知見を学生の方々にお伝えする機会を頂けることになった、という次第でした。

小町さんや研究室所属の学生の方々へヒアリングを行った結果、過去の学生の方々が実装してきたコードは、残念ながら読みやすさにあまり気が配られておらず、今のところ、研究室や大学の資産として有効には活用できていない状況にあるとのことでした。 この事を踏まえ、当社が過去に関わってきたセミナーやワークショップなどの中で、「リーダブルコードを実践すること」に焦点を当てた研修の内容を元に講義を行うことにしました。

ただ、以前別の機会でワークショップを実施した際には、「コードを新たに書く際にリーダブルにする」際の考え方は伝えられたものの、「リーダブルなコードを書くことを妨げる要因は何か、それをどう取り除けばよいか」については十分に掘り下げられていなかったために、リーダブルなコードを日常的に書き続けてもらえるようになったかどうかについては、残念ながら、十分な手応えを得られない結果となってしまいました。

折しも、この頃ちょうど、自社の業務で使っていた小規模なコードが、恥ずかしながらリーダブルとは言い辛い状態になってしまっていたことに気が付きました。 そこで今回は、そのコードがリーダブルでなくなってしまった経緯を振り返り、リーダブルな状態に改修する過程で得た知見を活かして、「リーダブルなコードを書きたくても書けない、書き方を学んでも実践できない」状況をもカバーした演習内容を目指すことにしてみました。

演習の構成

限られた時間を有効に使うため、事前課題として書籍「リーダブルコード」に一通り目を通しておいて頂くよう案内した上で、授業時間中は、なるべく書籍では述べられていないことを多く扱うようにしました。 この授業での体験を踏まえて、実際にコードをリーダブルにするためにどのような工夫が可能かのヒントを得るための資料として、書籍を都度参照してもらうという想定です。

演習授業は1コマあたり90分の授業を1日に2コマずつ実施し、計4コマ行いました。 それぞれのコマの内容は概ね以下の要領としました。

  • 1コマ目(1日目前半):座学:前提知識の説明
  • 2コマ目(1日目後半):グループワーク
  • 3コマ目(2日目前半):グループワーク
  • 4コマ目(2日目後半):グループごとに発表し、知見を全体で共有

ただ、実際には、オンライン参加のグループの環境設定に予想よりも時間がかかったために、2コマ目の実習時間を充分に取る事ができず、玉突き的に後のコマの冒頭にグループワークの時間を延長する結果となりました。

座学パート

この時間では、事前にアナウンスしておいた以下の3つのサンプルコードを受講者各自の手元に置いてもらいながら、スライドを用いて説明を行いました。

スライドは以下の物です( 口頭で補足した内容のメモを含むソース )。

スライドは、まず「リーダブルなコード」を書く意義を説明し、次に、「実際の現場でのリーダブルなコードの実践例」を示して、最後に「リーダブルでないコードの発生を防ぐ方法」を示す構成にしました。

リーダブルなコードを書く意義について

リーダブルなコードを書く意義は、書籍「リーダブルコード」でも紹介されていますが、事前に読んだ段階では内容をすべて理解できたわけではない人もいるはずと考え、このことを座学パートでも改めて説明しました。

質とスピードという有名なスライドでは、以下の2つの具体的な数字が紹介されています。

  • 自動テストを作成する場合としない場合では、4回目のテスト実行時点で損益分岐点を超えて、テストを書いた方が開発のスピードが速い状態になる
  • 保守性を高く保つ努力をした場合としない場合では、開発開始1ヵ月で損益分岐点を超えて、保守性を高く保つ努力をした方が開発のスピードが速い状態になる

このスライドでも、同様の趣旨で以下のような概念図を示した上で、(コードのリーダブルさは保守性に寄与する要素の1つであることから)後者の話を拡大適用して、やや大げさ目に「コードをリーダブルにする手間の損益分岐点は1ヵ月程度で訪れると言われています」と説明してみました1

(コードのリーダブルさを維持する場合とそうでない場合の開発スピードの概念図)

その上で、受講者の方々が実際に遭遇するであろう「コードを読む」「他の人のコードを引き継ぐ」「既存のコードをメンテナンスする」必要が生じる場面として、「他の人と共同研究する場合」「研究を発展させる場合」など、いくつか例を示しました。

ただ、今回の受講者の方々は、受講時点では「日常的にコードを書いているわけではなく、課題などで必要に迫られてコードを書くことがある。ただし、今後は研究の過程でコードを書く機会が増えると予想される」という状況にあります。 ほとんどの方にとっては「既存のコードをメンテナンスする」状況はあまり経験がなく、抽象的な話だけでは理解が難しくなることが予想されたため、今回は、より具体的な例を挙げて説明を加えていくことにしました。

リーダブルコードを実践するためにまず取り組むべきこと

「リーダブルなコードを書くこと」を実践するためには、コードを読む習慣を作ることが大前提となります。

読む習慣といっても、これはなにも「定期的にカーネル読書会に参加する」といった意味ではありません。 「コードは書く一方(書き捨てるだけ)ではなく、読んで内容を読み取る物でもある」という視点を持ち、その上で、「自分が書いたコードを頻繁に読み返す」習慣を身に着ける、ということです。

例えば、Excelワークシートを自動生成するにあたり、セルの結合処理として以下のようなことをしたいと考えたとします(以下はPythonの文法を模した擬似コードです)。

関数 項目の見出し列(B~C)のセルを結合する(行番号, 項目):
    項目の選択肢の数が1以下だったら:
        何もせず終了
    そうでないなら(選択肢が2つ以上あるなら)、
    「見出し列の定義の配列」の全要素について、 要素の番号 を使って:
        シートの範囲を結合(
          開始行 → 行番号,
            開始列 → 要素の番号,
          終了行 → 行番号 + 選択肢の数 - 1,
            終了列 → 要素の番号) 

コードを読む習慣が無いと、これを以下のように実装するかもしれません。

def m(s, n, i):
    if len(i['o']) <= 1:
        return
    s2 = s.s
    for i2, c in enumerate(hcol):
        s2.merge_range(n, i2,
                       n + len(i['o']) - 1, i2,
                       '')

記憶が鮮明なうちは、このようなコードであっても、コードを見て最初に示した「したいこと」の詳細を思い出すことができるでしょう。 しかし、このコードだけを渡された後任の人がコードを読んだときや、コードを書いた時のことをすっかり忘れた頃に自分で読み返したりした場面では、このコードは以下のように見えるはずです。

関数 何か(何か, 何か, 何か):
    何かの数が1以下だったら:
        何もせず終了
    そうでないなら、
    何か = 何かが保持している何か
    何かについて、 何か を使って:
        何かの範囲を結合(何か, 何か,
                         何か + 何か - 1, 何か)

これでは、「元々何をしたかったのか」は全く分かりません。 仮にどこかの動作がおかしくなっていたとしても、どこを変更すれば直るのか、その変更に悪影響はないのか、調査と検証に膨大な手間と時間を要してしまいます。 何か変更をしようと思う度にそんな苦労を強いられては、「開発を進める」どころの話ではありません。

では、リーダブルな書き方をするとどうなるでしょうか。 例えば、先の実装例のような名前付けではなく、それぞれの部分が意図しているものに即した名前付けをすると、同じ処理を以下のように書けます。

def try_merge_item_heading(self, row, item):
    if len(item['options']) <= 1:
        return
    sheet = self.sheet
    for index, _column in enumerate(HEADING_COLUMNS):
        sheet.merge_range(row, index,
                     row + len(item['options']) - 1, index,
                     '')

このように書かれていれば、後任の人や未来の自分がコードを読んだだけで、以下のようなことくらいは読み取れるはずです。

関数 項目の見出しを結合してみる(自分, 行, 項目):
    項目の選択肢の数が1以下だったら:
        何もせず終了
    そうでないなら、
    シートは自分が保持している
    見出しの列 の全項目について、 番号 を使って:
        シートの、範囲を結合( 行, 番号,
                        行 + 項目の選択肢の数 - 1, 番号,
                        '')

「コードを読む」だけでこれだけの情報を得られれば、その後の改修作業はずっとスムーズに進むでしょう。

コードがリーダブルになっているかどうかは、実際にコードを「読んで」みないと分かりません。 コードを「読む」習慣が身に付いていないと、自分で書いたコードを見ると、すぐに脳内の記憶を引き出してしまうために、「読」まないまま問題を見過ごしてしまいます。 リーダブルなコードを書くためには、「脳内の記憶を引き出すのをぐっとこらえて、前提知識を共有していない読み手の気持ちになり、まっさらな頭でコードを読み返す」練習をするのが、成功への近道と言えるでしょう。

2コマ目以降のグループワークでは、この一連のことを実感してもらうために、以下の演習を行いました。

  • 複数人のグループで、1つの課題の実装に取り組む。
    • これにより、「他の人が書いたコード」を「自分がまっさらな気持ちで読む」体験と、「自分が書いたコード」を「他の人にまっさらな気持ちで読まれる」体験をし、コードを書いた人が思っていることと実際のコードとの間のずれを意識してもらう。
  • 課題の実装にあたっては、各自が思いつく限りの「リーダブルにするための工夫」を盛り込み、どう工夫したかのメモを書き残す。
    • これにより、自分や他の人がどのような書き方をリーダブルと感じるかを言語化し、同様の場面で再適用しやすくする。
  • 後半では(同じ言語で実装を行っていたグループ同士で)他のグループと実装を交換する。
    • これにより、「コードやコミットログを読んで、実装の状況を把握する」体験をしてもらう。
    • 交換先の実装の進度が、自グループの実装よりも進んでいた場合であれば、「途中からプロジェクトを引き継いだ」体験を得られると期待される。
  • 交換先の実装の開発を継続する過程で、そこに込められた「リーダブルにするための工夫」を見つけてみる。
    • これにより、さらに広い範囲で「他の人の視点」と「自分の視点」での「リーダブルさについての考え方」の違い、視座の多様性を意識してもらう。
  • グループワーク後に、チームで意識した・交換先の実装から読み取った「リーダブルにするための工夫」を1つ選んで、全体向けに発表する。
    • これにより、さらに広い範囲で「他の人の視点」と「自分の視点」での「リーダブルさについての考え方」の違い、視座の多様性を意識してもらう。

実際の現場での「コードがリーダブルでなくなってしまった」「リーダブルになるよう改めた」実践例

前項までの話が頭に入っていれば、「新しくコードを書く際に、リーダブルにする」ことについては、練習次第で少しずつできるようになっていくと期待できます。

ただ、実際にコードを書き、運用するようになってくると、意図せず「リーダブルでないコード」が生まれてしまっていたことに、後から気がつく場面もあります。 そこで、次のパートでは、「そのような状況が何故発生してしまうのか」「発生してしまった状況からどのようにリカバリーを図るのか」を解説することにしました2

最初の実装

当社では、Firefoxを法人運用向けにカスタマイズするにあたって、法人運用で需要の多いカスタマイズの要望を、実現方法とセットで資料にまとめて管理しています。 この資料は当初は、LibreOfficeのスプレッドシートを原本として管理していましたが、管理の煩雑さや複雑化した要件への対応の難しさなどの理由から、現在はプレーンテキスト形式のソースから動的にExcelスプレッドシートを生成する方式になっています。

このスクリプトの最初の版は、Pythonが得意な人が、要件を満たす簡潔な実装として書いた物でした。それが以下です。

スクリプト全体では245行、主要な部分は170行ほどです。 ファイル中では10個の関数が定義され、最も長い関数の generate_xlsx は約59行あります。 プレーンテキスト形式のファイルから、かつて手作業で作成したのと同じレイアウトのExcelスプレッドシートを組み立てる、一種のコンバーターと言えます。 コードの意図を読み取りやすい名前付けや、(Pythonの言語特性のおかげで)整ったインデントなど、書籍「リーダブルコード」で紹介されている「このような書き方をするとリーダブルになる」書き方が実践されているのが見て取れるはずです。

講義では、このコードを参照しながら「『...→...での変更』という見出しの列に『新規』と出力する条件はどこで判定しているか?」を受講者の方々に実際に探してもらいました3が、このレベルの規模であれば、正解に辿り着けた方は多かった様子でした。

リーダブルでなくなった実装

その後、新たに生じた要件4に対応するために、Pythonに明るくなかった筆者が見よう見まねで手を加えた結果、リーダブルさが損なわれてしまった物が、以下のバージョンです。

このバージョンは、関数の数は変わらず10個のままですが、行数は全体で1.2倍程度に増加しました。 変更箇所は主に2つの関数 write_headergenerate_xlsx に集中していて、特に generate_xlsx は行数が89行と、元の1.5倍に増加し、いわゆる「肥大化した関数」の様相を呈しています。 また、generate_xlsx の変更の結果 iffor のループのネストが深く複雑になり、一目見て動作を把握するのは難しい状態になってしまいました。 こういった傾向は、「リーダブルでないコード」に見られる典型的な特徴と言えます

講義では、このバージョンを参照しながら「『検証手順書対応番号』の列に出力する内容はどこで決まっているか?」を受講者の方々に実際に調べてもらいました5が、今度は、先の例に比べて正解に辿り着けなかった方が多かった様子でした。

何故コードがこのような状態になってしまったかというと、本質的には、スクリプトに期待される役割・スクリプトの目的が、追加の要件4によって「単純なコンバーター」の領域を大きく超える物に変わっていたにも関わらず、設計の見直しを避けて、「単純なコンバーター」を想定した設計を維持したまま、無理矢理に最終出力だけを要件に合わせようとしたためだった、と筆者は見ています。 この点についての詳しい考察は、後で改めて語る事にします。

リーダブルさを取り戻すための改修

経緯はともかく、コードがリーダブルでない状態になっていたのは間違いありません。そこで、先のバージョンの機能を維持したまま、なるべく「リーダブル」になるように筆者が設計を大幅に改めた物が、以下のバージョンです。

このバージョンでは、前のバージョンでは使っていなかったクラス記法を使っており、2つのクラスと28個のメソッド・関数が存在する状態となっています。 設計を改めた結果、行数は461と、1つ前のバージョンから1.5倍に増加しています。 機能的にはほぼ変わっていないということは、このバージョンでは、前のバージョンから関数やメソッドが細かく分割されていると言えます。

講義では、このバージョンを参照しながら「『検証手順書対応番号』の列の右隣に『検証済み』という列を追加するには、どこに手を入れればいいか?」の調べ方を、講師の手で以下のように実演しました。

  1. 「検証手順書対応番号」という文字列が含まれている行を探す。(121行目
  2. その行は定数 VERIFICATION_COLUMNS として定義されている配列の一部なので、今度は VERIFICATION_COLUMNS をコード内検索し、参照箇所を探す。(171行目246行目
  3. 171行目で呼び出されているメソッド _write_header_columns の名前をコード内検索し、メソッドの定義箇所を探す。(174行目
  4. メソッドの定義を見て、引数として渡ってきた配列をループで処理して列を生成している様子を確認する。
  5. 246行目で呼び出されているメソッド _write_item_columns の名前をコード内検索し、メソッドの定義箇所を探す。(252行目
  6. メソッドの定義を見て、引数として渡ってきた配列をループで処理して列を生成している様子を確認する。
  7. 4と6で明らかになった設計から、121行目VERIFICATION_COLUMNS として定義している配列の、「検証手順書対応番号」というラベル文字列を含む項目と、その次の項目の間に、「検証済み」というラベル文字列を含む項目を追加すれば、それが列として出力されると判断する。

このとき注意したのは、「全体を通して読むのではなく、手がかりが得られている箇所と関連箇所だけを読むようにした」ということです。

業務で使うソフトウェアは、通常、開発期間より運用(使用)期間の方がずっと長く、コードを「改修する」機会が度々発生し、その度に、改修に関係する箇所だけを読むことになります。 そのため、このバージョンはそういう読み方をする状況で「読みやすい」と感じられるコードにした、というわけです6

このような「状況の変化に合わせてコードを書き換えていくこと」を体験してもらうために、2コマ目と3コマ目のグループワークでは、「1つの実装に対し、追加の要件が次々と発生していき、都度実装の改修が必要になる」ような演習課題を設定しました。 これは、以下のような効果を期待してのことです。

  • 既存の実装を書き換える必要に迫られることで、コードを変更することの抵抗を減じられると期待できる。
  • 交換先の実装の進度が、自グループの実装よりも手前だった場合であれば、「先の仕様を見据えながら、よりリーダブルな書き方を意識して実装する」体験を得られると期待できる。

ただ、今回のグループワークではTA側にこの事を充分に周知できていなかったため、実際には、「課題1」「課題2」といった要領でそれぞれ別の実装を作成してしまったグループが複数ありました。 今後同様のワークショップを開催する際は、この点を改めて強調したり、課題の最初の数段階を講師がデモンストレーションしたりと、想定から外れた作業の仕方にならないよう誘導する工夫が必要そうです。

コードがリーダブルでなくなっていってしまう要因

壊すのが怖くて、見て見ぬフリ

先ほど、コードがリーダブルでなくなった原因を、「要件が変化し、要件とコードの設計が合わなくなってきたにもかかわらず、設計を見直すことを避けて7、無理やりな改修を重ねてしまったせいだった」と考察しました。 何故そのようなことが起こってしまうのでしょうか。

埋没費用効果8の面からも説明はできそうですが、筆者は、端的には「既存のコードを書き直すのが怖かったから」という言い方で説明が付くと考えています。

  • 最初の改修をした時点の筆者は、Pythonのごく基本的な機能しか把握していませんでした。 他の言語での開発の経験から「クラスを使った方がよさそうだ」と見当は付いたものの、実現方法が分かりませんでした。
  • 素直にPythonでのクラスの書き方を勉強すればよかったのですが、改修の必要が生じた時点ではスケジュール的な余裕があまりなく、筆者は精神的余裕の無さから、未知の新しいことに挑戦するより、ダサくても既知の技術の範囲で確実に要求を満たすことを優先したいと考えてしまいました。
  • クラスを使わずに設計されたコードを、機能を維持しながらクラスベースの設計に改めるという変更は、極端な言い方をすると、「同じ入力に対して同じ出力を返す、まったく別の互換実装の新規開発」にも相当します。 当時の筆者には、それが本当に自分にできることなのかどうかすら、判断が付きませんでした。

こういった背景から、筆者は「既存のコードを壊すのが怖い」という感覚に囚われ、既に動いている部分には極力手を触れず、なるべく狭い範囲の変更だけで既知の技術の範囲でどうにかして要求を満たそうとして、「巨大なデータを関数の引数であちこち引き回す」「制御構文を何重にも入れ子にする」といった、リーダブルの対極にあるようなコードを書いてしまったのでした。

そういったコードが、極めて近い将来に保守性に悪影響を強く及ぼすことは、筆者にも予想できていましたが、「他にやりようがないから」と自分に言い訳をして、良くない兆候を見て見ぬフリしていたわけです。

恐怖を克服するために

コードが恐怖のせいでリーダブルでなくなっていってしまうのであれば、恐怖を克服できれば、コードをリーダブルな状態に保てるはずです。

そのために是非とも取り組みたいのが、「書き直せない」という恐怖を払拭するための仕組み・技術的な工夫です。 例えば、以下のような工夫はよく知られています。

  • 自動テストを書く。これにより、「気付かないうちにどこか別のところが壊れていて、そのことを見落としたままリリースしてしまった」というトラブル(後退バグ)を未然に防げます。
  • GitやMercurialなどを使って、コードをバージョン管理する。これにより、最悪でも「最後に動いていた状態」までは立ち戻ることができます。また、コミット履歴はコードの変更の経緯を調査するヒントになり、不要になったコードを安心して削除できるようにもなります9
  • 静的文法チェッカーやコードフォーマッター10を使う。これにより、ケアレスミスの混入を防げますし、コーディングスタイルの統一も図れます11
  • 作業量を見積もる際に、必ずリーダブルな状態にコードを整理するための時間を計算に入れて数字を出す12。これにより、「今は時間がないからコードをリーダブルにできない」となってしまうことを防ぎやすくなります。

また、このほかに心構えとして以下のことも紹介しました。

  • 書いたコードをマメに読み返す。読み返す機会が多いと、「この部分、読みにくくなってきたな……」と、コードがリーダブルでなくなってきている兆候に気付きやすいです。
  • 理解できていないコードを、理解できていないまま入れない13。そういったコードは「1文字でも書き換えると動かなくなる」アンタッチャブルな存在になってしまいがちです。そうならないよう、プログラムの中に含めるコードは、常に「これはこういう意図で書かれた」「これはこういう理由で必要」と説明できる状態を保ちましょう14

すぐに書き直した方が、結局は一番ラク

こういった工夫で安全を確保した上で、「マメにコードを読み返して、リーダブルでなくなってきた兆候に気付いたら早急に書き直す」ということを繰り返し行い、且つ継続することこそが、リーダブルコードの実践です。

開発の経験が浅いと、「大変なことだ」と感じるかもしれません。 ですが、ベテラン開発者は「そうしておいた方が後で楽だ」「そうしないと後で絶対に痛い目を見る」ということを骨身に染みて知っています。

筆者は、これは「掃除」や「後片付け」に似ていると考えています。 部屋の床にこぼしたコーヒーの汚れや、料理をするのに使った包丁やまな板の汚れは、時間が経過すると、床材に深く染み込んだり、化学変化を起こして固着したり、錆になったりして、どんどん取れにくくなっていきます。 でも、汚したときにすぐに掃除すれば、サッと拭き取るだけで綺麗になります。 コードの「よくない兆候」も同じで、放置すればどんどん直しにくくなっていきます15が、早めに対処すれば、簡単に直せることが多いです。

今回の講義は、受講後の提出課題として、「過去に授業や個人の趣味などで開発した何らかのコードについて、リーダブルになるよう書き直して、どこをどのようにリーダブルにしたかを説明する」という課題を設定しました。 課題を提出して下さった方の中には、「ちょっと前に実装した物なのに、もうコードの意図が分からなくなってしまっていた」とコメントされていた方もいらっしゃいました。 この提出課題を通じて、早め早めのリカバリーが有効であることを実感してもらえていれば何よりです。

授業実施時の工夫と反省

今回は、全体で30名ほどの方に受講頂き、1/3が対面での参加、2/3がオンラインでの参加となりました。 新型コロナウィルスの感染が懸念される状況ではありましたが、受講者の方々へのきめ細かなサポートは対面授業の方が実施しやすいことと、ワクチン接種が進んでいることを鑑みて、希望者のオンライン受講を可能としつつ対面授業も並行実施する、ハイブリッド形式の授業としました。

運営側は、講師(筆者)、TAとして当社の足永Speeeの大場光一郎さん、大学側で招集頂いたTAの方々、オブザーバーとして小町さんという編成で臨みました。 また、今回は受講される方の中に聴覚に障害のある方がおられたことから、手話通訳の方も手配頂きました16

対面参加とオンライン参加の両対応の仕方

オンライン受講のインフラとしては、大学側で既に採用されているZoomを使用しました。 Zoomには「ブレイクアウトルーム」という、ミーティング参加者を小規模のグループに分ける機能があるため、グループワークはブレイクアウトルームで実施し、オンラインのTAは共同ホストとして各ブレイクアウトルームを行き来する形としました。

対面授業側のTAが不足していたことから、講師は対面授業側に出席し、以下のようにして対面参加者とオンライン参加者の両方に情報を届けるようにしてみました。

  • スライドの見せ方は、ラップトップPCの画面をZoomで画面共有しつつ、画面を教室のプロジェクターで表示することにしました。
  • 教室が小さめだったことと、教室のスピーカーで発せられた音声をラップトップPCのマイクで拾うと音割れする様子だったことから、全体向けに喋る際は、講師はラップトップPCの前で肉声で喋ることとしました。
  • 受講者による全体向けの発表時は、対面授業側で喋る人には教室のマイクを使ってもらい、ラップトップPCのマイクによってオンライン参加者に声を届けることにしました。オンライン側で喋る人には、Zoomで喋ってもらった音声をラップトップPCのスピーカーで出力し、それを講師が教室のマイクで拾って、教室のスピーカーから流す形としました。

この形態で実施したところ、講師が対面参加者のサポートを行っている間は教卓のラップトップPCから離れざるを得ず、以下のような問題が浮き彫りとなりました。

  • 講師がオンライン側受講者のサポートをまったく行えない。
    • 特に、ブレイクアウトルームにいる受講者からのサポート要請は、通知という形でミーティングの(共同ホストではなく)メインホストのみに伝わるため、講師がメインホストになっていると対応できない。
  • 講師がブレイクアウトルームの管理を行えない。
    • 時間が来てもブレイクアウトルームからメインルームに全員を戻す操作を行えない。
  • TAの人数がグループの数を下回っていたために、TAがサポートを行えていないグループが発生してしまう。
    • 対面側はTAと講師が流し見できるが、オンライン側は明示的なルーム移動の必要があり、タイミングを逸してしまいがちになる。
      • 講師がそもそもこの状態に気付くことができないため、TAのルーム移動を指示できない。
    • ブレイクアウトルームからのサポート要請が講師に届かないため、対応できない。

これらの問題については、当日の判断で小町さんに以下のようにご対応頂きました。

  • 講師ではなく小町さんをメインのホストとし、ブレイクアウトルームの作成・メンバー移動やTAの移動などを対応頂く。
  • オンライン側のTAについては、最長15分単位で強制的に参加ルームをローテートし、長時間TAがいないままの状態のルームが発生しないようにする。

今回は講師がZoomのミーティングをホストし慣れていなかったこともあり、小町さんにミーティングの管理を全面的にお任せする形となりました。 次の同様の機会には、講師がメインホストとしての対応に集中するか、もしくは、今回小町さんにご対応頂いたような作業を専任で行うオペレーター役を設ける必要がありそうです。

グループワークの運用

先述した通り、今回の演習はグループワーク形式としました。 これまでに実施した同様の趣旨のワークショップでは、参加者が企業で働くソフトウェア開発者や、ソフトウェア開発を志望する学生など、それなりに開発経験がある方だったため、「個人個人で課題を実装して、途中で他の参加者と実装を交換する」形式としていました。 しかし、今回は以下の事情から、その形態での開催は難しいと判断しました。

  • 今回の想定受講者はそこまで開発経験が豊富なわけではない方が多い。
  • 受講者ごとに1体1でサポートできる人数のTAは確保できない。
    • 今回はオンライン参加の方が多くなる可能性があり、オンラインでは「TAが複数の受講者を流し見する」対応も取りにくいと予想された。

4名以上のグループになると「話に参加できず、見ているだけになってしまう」人の発生率が上がる傾向がある旨を小町さんから伺ったため、グループは最低2名・最大3名で編成し、講師を含むTAが1人あたり最大2つまでのグループのサポートを行う想定で、TAの方を確保して頂くことにしました。

  • グループ編成は、事前に「得意な開発言語を3つまで挙げる」という形でアンケート調査を実施し、先述の人数構成になるように、且つ、他のチームと実装を交換できるようになるべく同じ言語のチームを2つ以上設けるよう、調整を行いました17
  • 「グループの全員がまったくの未経験で、何も作業を進められない」という事態の発生を防ぐことを目的として、事前に開発やコーディングの経験について確認し、グループ内に最低1人はそれなりにコーディング経験がある人がいる状態としました。

対面授業であれば「1つの画面を覗き込みながら、グループの各人がコメントをし合い、必要に応じて各メンバーがキーボード操作担当を入れ替わりながら交代交代でコードを書く」ということをそれほど苦労せず行えるのですが、オンラインでこれを代替するには、工夫が必要です。

  • ZoomやDiscordでは1人の画面をルーム全員で共有して見る事はできますが、キーボード操作まで乗っ取ることはできません。そのため、「コードのここを、このように書き換えたい」という意図を齟齬無く伝えるには、チャット経由でコードをコピー&ペーストするといった方法を取らなくてはなりません。
    • Visual Studio Codeの普及率が高いようだったので、今回の講義では受講者全員にVisual Studio CodeとLive Share拡張機能をセットアップしてもらい、キーボード操作担当者がコラボレーションセッションのホストになって、他のメンバーはそのセッションに参加する形を取ってもらいました。
    • グループのメンバー同士ではLive Shareの画面を通じて状況を共有できていても、後からルームに参加したTAには画面が見えないことから、キーボード操作担当の方には、画面をルーム内で共有して頂くようにしました。
    • Live Shareでの作業画面共有が上手く行かなかったグループがいくつかあり、それらのグループについても、Zoomの画面共有とチャット・音声でのやり取りで代替してもらうように案内しました。
  • グループ内でのコミュニケーションは音声通話が主になるため、聴覚に障害のある受講者の方は困難が予想されます。
    • この点は、先述の通訳16の方に同じブレイクアウトルームに参加して頂いて対応しました。
    • 当日は、その方が割り当てられたグループの受講者が2名だけだったことから、グループ内でのコミュニケーションには1対1のチャット(DM)も使用されていたようです。

今回の授業では「課題を実装し、手元で動作させる」必要があったことと、全員が全員GitやGitHubの扱いに慣れていたわけではなかったことから、「ローカルで実行可能なコードを」「複数人でオンラインで編集できる」ツールとしてLive Shareを試してみました。 ただ、それ自体の扱いが難しかったためか、使用言語や環境との相性なのか、期待通りのコラボレーションの実現に至れないケースが生じてしまい、今回の授業でのグループワークの作業時間を圧迫する要因となってしまいました。 授業のオンライン化の需要が高まっている今だからこそ、プログラミングに関わるグループワークのリモート実施のベースラインを揃えるために、Live Shareやそれに類する仕組みそのもののトレーニングにも時間を割いた方が良いのかもしれません。

まとめ

東京都立大学で実施したリーダブルコード演習の概要紹介を通じて、リーダブルなコードの実践例と、コードがリーダブルでなくなってしまう原因への立ち向かい方の例、および、今回得た対面・オンラインのハイブリッド形式でのグループワークの実施ノウハウをご紹介しました。

コードは、「書いて、動いた」で終わりではなく、リーダブルにすることで、資産としての価値が生まれます。 書いて、リーダブルに直して、また書いて、というサイクルを回すことが大事で、このサイクルが充分に早く回るようになった状態が、「書いたコードが自然とリーダブルになっている」状態と言えます。

慣れないうちは「コードをリーダブルにする」ことにまどろっこしさを感じてしまうかもしれませんが、そうした積み重ねは確実に自分の技術者としての資産となります。 リーダブルコードの実践に関心を持ってこの記事をご覧になられた方は、是非とも意識して、リーダブルなコードを書くこと、コードをリーダブルに保つことを継続してみてください。

当社では、業務プロセス改善のお手伝いとして、今回のようなリーダブルなコードを書くことの実践の仕方の研修のほか、

など、自由なソフトウェアの開発の過程で私達が培ってきた経験をお伝えすることのご相談を承っております。 OSS・自由なソフトウェア開発のフィールドで見られる技術的ノウハウを組織内へ取り込みたいとお考えの方は、是非当社までお問い合わせ下さい

  1. ただ、筆者の個人的な感覚では、コードのリーダブルさを意識しなかった場合の開発速度低下は、コードを書いた翌日にはもう顕著に表れてしまうくらいの感覚があります。

  2. これは、過去のリーダブルコードワークショップにはなく、今回新たに追加した部分です。

  3. 実は、「新規」という文字列はこのファイルの中には1箇所しか存在しません。これをファイル内で検索すると、182行目statusという変数に代入されている箇所が見つかります。ここに至るまでのif文が問いの答えで、正解は「173行目から181行目にかけての条件文」ということになります。なお、正攻法で列番号から探す場合は以下の通りです:「...→...での変更」という見出しは123行目で出力されており、この sheet.write() の第2引数から、当該列は8列目であることが分かります。同じ列番号を指定してセルの内容を書き出している箇所が196行目にあり、セルの内容として出力される変数 status の値は、その直前の173行目から181行目にかけての複数のifの中で代入されている、と分かります。

  4. 「前のバージョンでの設定」と「現行バージョンでの設定」の1体1の比較のみで充分だったのが、「前のバージョンでの設定」と「現行バージョンでの設定(デスクトップPC用)」「現行バージョンでの設定(ラップトップPC用)」のように1体多の比較が必要になった、という状況でした。

  5. 「検証手順書対応番号」という見出しは141行目で出力されており、この sheet.write() の第2引数から、当該列は col_count+1 列目であることが分かります。これと同じ列の指定の仕方をしている箇所が245行目にあり、セルの内容としては、変数 chapter の値が出力されています。この変数の値は211行目220行目の2箇所で代入されていることから、正解は「211行目と220行目」となります。1つ前の問いの「正攻法」にあたる方法でないと正解に辿り着けない、という意地悪な問題でした。

  6. これは、このような方針がいついかなるときも最良であることを意味しません。筆者を含むクリアコードの関係者がリーダブルなコードについて話すときには、「リーダブルさに絶対的な基準は無い。読む人や読む状況によって、何がリーダブルと感じられるかは変わってくる」ということをよく述べます。この事例もまさにそうで、人によってはこの改修後の版を読みにくいと感じるかもしれず、そのことにはくれぐれも注意が必要です。

  7. 当日の講義では解説を省略しましたが、「複雑な問題を解くには、コードも相応の複雑さにならざるを得ない」という点には注意が必要です。解くのが困難な複雑な問題でも、より小規模な複数の問題に解きほぐして分割できれば、解くべき個々の問題は単純になり、コードもよりリーダブルに書きやすくなります。しかし、分割不能な本質的に困難な問題に遭遇した場合、このやり方では太刀打ちできません。筆者は、そのような状況は、より高度なことを学ぶ必要が生じている場面だと考えています。書籍「リーダブルコード」で取り扱われている範囲は、言うなれば小中学校で学ぶような基礎的リテラシーの範囲だと例えることができ、それだけを学んでいても「物理学の論文」を書くことはできないのと同様に、「リーダブルコード」の範囲を超える複雑な問題に遭遇したときは、相応の知識を学ぶ必要があると言えます。「どんなに頭をひねってもコードが一定以上読みやすくならない」と感じたときは、自分は次のステップに学習を進めるべきタイミングに到達した、と考えることをお薦めします。

  8. 既存のプログラムコードの開発コストという、既に拠出済みの「費用」について、もはや回収は不可能で見切りをつけた方が合理的であっても、費用を回収したいという欲求が働いて、合理的でない判断をしてしまうこと。

  9. 「不要に思えて削除したコードが実は必要な物だった」ということはよくあり、これは後退バグの最も典型的な発生原因です。しかし、後退バグを恐れてあらゆるコードを維持し続けてしまうと、コードは肥大化する一方となります。バージョン管理システムを使って個々の変更をきちんと記録していれば、個々のコードについて「このような理由で追加された」という経緯を辿れるため、後退バグの発生を防ぎやすくなります。

  10. ESLintやPrettierなどが有名。Go言語のgofmtのように、言語標準のツールとしてフォーマッターが提供されている場合もあります。

  11. コードフォーマッターでは、コーディングスタイルのルールを設定として明文化できるため、人によって判断がぶれることがないのもメリットです。

  12. 大学院生向けの講義だったこともあり、今回の講義ではこの点は紹介しませんでした。

  13. 例えば、公開記事からのコピー&ペーストなどで丸ごと持ってきた、自分の技術レベルを超えたコードや、試行錯誤の果てに訳が分からなくなってしまった(なのに動く)状態のコードなどがそうです。

  14. 記憶しておくのが難しければ、コメントで意図を書き残すのも有用です。

  15. よくない書き方の部分に依存した新しいコードが増えていけば増えていくほど、書き直しが必要な範囲も、書き直しの影響の範囲も広がっていき、書き直しは困難になります。

  16. 東京都立大学はダイバーシティ推進に力を入れておられるそうで、本件も合理的配慮を必要とする学生の就学支援の一環として、大学側で対応を頂けました。なお、文脈が不明な状態での同時通訳は一般に困難なことから、スライド資料は、口頭で説明する予定の内容も含めて文字として書き起こした上で、事前に大学を通じて通訳の方へ共有しています。

  17. 1チームだけ、他に同じ開発言語のチームが無い状態が生じてしまいましたが、こちらは第2候補に選択した言語に基づいて別チームと実装を交換する形としました。結果的には、「あまり詳しくない言語だったけれども、コードがリーダブルに書かれていたためスムーズに引き継げた」という体験をしてもらえたようだったので、これはこれで良い効果があったと言えるかもしれません。