SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Developers Summit 2024 セッションレポート

生成AI時代のプログラミングとの向き合い方とは? AIを活用した実装力を養うヒント

【15-A-7】AI時代を乗り切るための実装力をつけよう


  • X ポスト
  • このエントリーをはてなブックマークに追加

ループとStreamの例から、プログラミングの核心を理解する

 ここで、逐次実行についての理解度をチェックするために、きしだ氏はループ(繰り返し処理)を例に解説を始めた。「はじめは簡単だと思っていても、どこかの段階で『難しい』と感じるはず」ときしだ氏。どの部分で引っ掛かったかによって、プログラミングの基礎理解度が測れるわけだ。

 まずは、ループの中で最も簡単な、同じことを無限に繰り返す処理だ。サンプルコード[1]では、"Hello"という文字列を無限に出力する例が示されている。(System.outの省略は9月にリリースされたJava 23で可能になっている。)

サンプルコード [1] :
サンプルコード[1]:"Hello"の文字列を無限に出力するループ

 次のサンプルコード[2]は、回数が決まっているループだ。このようなCスタイルの構文において、同じことを何回か繰り返す構文はやや複雑なため、初心者には理解が難しい場合がある。

サンプルコード [2] :0回目から5回、
サンプルコード[2]:0回目から5回、"Hello"の文字列を出力するループ

 そこで、サンプルコード[2]をより分かりやすく記述したのが、サンプルコード[3]である。JavaのIntStream.rangeメソッドを使用することで、処理が直感的に理解しやすくなる。

サンプルコード [3] :サンプルコード [2] と同じ処理をIntStream.rangeで表現したもの
サンプルコード[3]:サンプルコード [2] と同じ処理をIntStream.rangeで表現したもの

 次のサンプルコード[4]は、処理にループの変数を使うという例だ。ループの中にあるカウンタを使うため少し難しくなり、ここで躓いてしまう人もいる。

 「ループ変数は状態なので、 [println] が実行されるたびに {i} の値が変わる。この部分が逐次実行されていることが分からないと、理解が難しい」(きしだ氏)。

サンプルコード [4] :0回目から5回、
サンプルコード[4]:0回目から5回、"Hello (ループ変数)"の文字列を出力するループ

 またサンプルコード[5]は、回数の指定ではなく、リストをループで処理する例も示された。リストに含まれる要素全てを大文字に変換し、出力するものだ。

サンプルコード [5] :リストの全ての要素を大文字にして出力するループ
サンプルコード[5]:リストの全ての要素を大文字にして出力するループ

 ここまでがループの基本的な処理であり、「つまずく人はそれほど多くない」。ところが、次に示すループによる値の集約になると、「入門者でなくとも、書けない人がたまに出てくる」ときしだ氏は話す。

 たとえばサンプルコード[6A]は、「リストに入っている数値を合計する」という問題だ。リストに入った数値が1つずつresultに加算されていき、ループのたびにresultの値が変わっていくという処理がなされている。

サンプルコード [6A] :リストの要素の値を全て加算していくループ
サンプルコード[6A]:リストの要素の値を全て加算していくループ

 この処理が理解できない場合、傍目からは「変数が分かっていない」ように見えてしまう。しかしながら実際には、ループが毎回逐次処理されることがイメージできておらず、resultの値が変わることの意味が分かっていない状態なので、いくら変数について説明されても理解は進まない。

 なおかつ、コードを丸暗記して済ませてしまうと、「リストの要素が条件を満たすかチェックする」「条件を満たす値のリストを作る」といった応用問題には対応できない。これら3つは「ループによって値を集約する」という、共通した構造を持つにもかかわらずだ。

3つの問題は、実は共通した構造を持つ
3つの問題は、実は共通した構造を持つ

 なお、ループによる値の集約は、Streamに置き換えて記述することも可能だ(サンプルコード[6B])。値を加算する場合には、sum()と書くだけで簡単に合計が出る。

サンプルコード [6B] :サンプルコード [6A] をStreamに書き換えたコード
サンプルコード[6B]:サンプルコード[6A]をStreamに書き換えたコード

 サンプルコード[7A]は、サンプルコード[6A]の応用として、リストの要素が全て奇数かどうかを判定する処理を示したものだ。resultの値とループのnが奇数かどうかを判定し、その結果をresultに代入している。

 こちらの例も、Streamに置き換えて記述ができる(サンプルコード[7B])。条件を全て満たすかどうかの判定には、allMatch()というメソッドを使用しているのがポイントだ。

サンプルコード [7A] :リストの要素が全て奇数であるかどうかを判定するループ
サンプルコード[7A]:リストの要素が全て奇数であるかどうかを判定するループ
サンプルコード [7B] :サンプルコード [7A] をStreamに置き換えたコード
サンプルコード[7B]:サンプルコード [7A] をStreamに置き換えたコード

 またサンプルコード[8A]では、IntStream.builderを使用して、リストの中から偶数の数値だけを動的に構築する例も示された。Streamに置き換える場合には、toArrayメソッドを使用してリスト生成を行っている。

サンプルコード [8A] :リストの要素のうち偶数だけを動的に構築するループ。最終的に偶数を新たなリストとして出力する
サンプルコード[8A]:リストの要素のうち偶数だけを動的に構築するループ。最終的に偶数を新たなリストとして出力する
サンプルコード [8B] :サンプルコード [8A] をStreamに置き換えたコード
サンプルコード[8B]:サンプルコード[8A]をStreamに置き換えたコード

 サンプルコード[6A]と[6B]、[7A]と[7B]、[8A]と[8B]をそれぞれ比較してみると、ループを使用したコードよりもStreamを使用したコードの方が可読性が高く、処理を追いやすいことが分かる。

 きしだ氏はStreamの利点について、「宣言的にコードを書けるところだ」と説明する。人間はどうしても処理を追うことが苦手なので、ループを追いかけるよりも、宣言的なコードを読むほうが理解しやすい。

 ただしStreamは、前後のデータに依存する処理には向かない、ときしだ氏は補足する。例えば移動平均は、前後の複数のデータの平均を取り、その範囲をどんどんずらしていくというものであり、Streamの使用には不向きだ。(なお、2024年3月にリリースされたJava 22では、Gathererによって前の値を踏まえたStreamの処理が行えるようになった。)

 講義はさらにレベルアップする。きしだ氏は「ここまでの説明では、データをそのまま使ってきたため比較的分かりやすかったかもしれない。しかし、隠れた状態を使うとなると、理解はさらに難しくなる」と前置きし、 [abc(def)ghi] と [abc((def)ghi] という2つの文字列から、対になるカッコを判別する処理を例示した。前者はカッコが1対になっているのに対し、後者は閉じカッコが1つ多くなっている。

 このようなカッコの対応を判定する場合、カウンタを使う方法が有効である。具体的には、開きカッコが来たらプラス1、閉じカッコが来たらマイナス1するというものだ。最終的にカウンタが0以下になれば、閉じカッコが多いことになり、1以上で終われば、閉じカッコが少ないということになる。

 「このような『隠れた状態』さえ見つけられれば、コードを書くのは簡単だ。ただしこの場合、コードだけを見てもカウンタの意味は分からないので、コメントを残す必要がある」ときしだ氏は補足する。

サンプルコード [9] :カッコの対応の有無を判定するコード
サンプルコード[9]:カッコの対応の有無を判定するコード

 さらにきしだ氏は、複雑な状態遷移について触れる。たとえばある数が10進数の整数であるか(「00」から始まらないかどうか)をチェックするには、以下の3つのという処理を行わなければならない。

  • 最初の文字が「0」の場合、「ZERO」状態に遷移
  • 最初の文字が「1〜9」の場合、「INT」状態に遷移し、その後の文字が「0〜9」であれば「INT」状態に留まり続ける。
  • すべての文字列について状態遷移が完了すると、終了状態(EOL)になる

 きしだ氏によれば「これを表現するコードを書くこと自体は簡単だが、そもそもの状態遷移を理解することが難しいため、はじめからコードを書くのではなく、状態遷移図で整理することをおすすめしたい」と話した。

入り組んだ状態遷移は、状態遷移図で整理する
入り組んだ状態遷移は、状態遷移図で整理する

 ここできしだ氏は、「私たちの日常は、状態遷移に溢れている」と話す。たとえば業務処理における購入、入金、配送という流れは、まさに状態遷移の一例だ。これらを個別に処理してしまうと、「何らかの例外があった際に、場当たり的なフラグを次々と生成してしまい、最終的には破綻する」。一連の流れを状態遷移として捉え、管理することで破綻のないコードが生み出されるというわけだ。

次のページ
逐次実行と再帰によるループの応用

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Developers Summit 2024 セッションレポート連載記事一覧

もっと読む

この記事の著者

丸毛 透(マルモ トオル)

インタビュー(人物)、ポートレート、商品撮影、料理写真をWeb雑誌中心に活動。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

CodeZine編集部(コードジンヘンシュウブ)

CodeZineは、株式会社翔泳社が運営するソフトウェア開発者向けのWebメディアです。「デベロッパーの成長と課題解決に貢献するメディア」をコンセプトに、現場で役立つ最新情報を日々お届けします。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

水無瀬 あずさ(ミナセ アズサ)

 現役エンジニア兼フリーランスライター。PHPで社内開発を行う傍ら、オウンドメディアコンテンツを執筆しています。得意ジャンルはIT・転職・教育。個人ゲーム開発に興味があり、最近になってUnity(C#)の勉強を始めました。おでんのコンニャクが主役のゲームを作るのが目標です。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/19783 2024/10/01 17:14

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング