リスク認識における三現主義の重要性

 エンジニアリング活動で好きな言葉に三現主義があります。三現主義は「実際に場で物を観察し、実を認識する」という問題認識についての考え方です。現場・現物・現実の三つの現から、三現主義と命名されています。

 この三現主義はソフトウェアの品質管理/品質保証の活動でも全般的に通用する普遍的なグッドプラクティスです。
 その一つとして、ソフトウェア品質リスクのマネジメントで重要な考え方になります。これは、次の2つの理由から、リスクの認識に三現主義が求められるためです。

リスクレベルと実際のリスクの乖離への対応のため

 理由の一点目として、ソフトウェア開発における品質リスクは本質的に定量化困難であることが多く、不明瞭で不確実性を伴う点が挙げられます。

 というのも、ある程度精度をもって発生頻度・発生確率を定量化できるリスクは、計測を通して統計的にそれらを定量化できるランダム故障など、ハードウェア固有のものが多くなります。ソフトウェア開発におけるリスク事象は、それに該当しないパターンが多く、リスクレベルの判断に主観判断が入り込みやすい傾向があります(法規制の中には、この特徴から、ソフトウェアバグ起因のリスク事象は発生確率100%として評価することを要求するものがあります)。
 すなわち、ソフトウェア開発における品質リスクのリスクレベルは、定量管理できるように無理やりレベルに当てはめただけのものが多く、多くの場合でリスクの本質を表現していません。数値化されたリスクレベルと、実際のリスクレベルが乖離している状況に注意を向ける必要があります。
 そのため、適切にリスクをマネジメントするためには、特に重大なリスクについて、表層的なリスクレベルだけ見て満足するのではなく、自らの肌感覚で、リアルなリスクの実態を把握しておく必要があります。すなわち、三現主義に基づくリスク認識が求められます。

流動的・潜在的なリスクの兆しに気づくため

 二点目の理由として、ソフトウェア開発では、変動制・不確定性が高いリスクの早期認識が求められる点が上げられます。
 というのも、金型や製造プロセスなどに縛られるハードウェア開発と違って、ソフトウェアは開発ライフサイクル全体で変更・改善・デバッグが継続されます。そのため開発ライフサイクルを通じてリリース前後でも品質リスクは流動し、かつ不明瞭性・不確定性を含みます。
 そのような状況下でコントロールが必要な重大な品質リスクを見逃さないためには、リスクレベルという単純化された値を通して開発を見るだけではなく、実際の生の品質リスクの兆しを把握する必要があります。すなわち、三現主義でソフトウェア開発のリアルを認識し、品質リスクの本質的な兆しを見逃さないようにする必要があります。

リスクマネジメントは数値上のリスクレベルのコントロールではなく、実際のリスク事象への対応が必要

 前述の通り、ソフトウェア開発ではリスクレベルは無理やり定量化した問題を持つ指標になりがちです。そのため、たとえ上位の管理者であっても、品質リスクマネジメントでは、リスクレベルの数値上のコントロールに閉じこもるのは危険です。本質的な品質リスクを三現主義で認識し、本質的にコントロールする必要があります。

ソフトウェア開発に合わせたSDCAサイクルのSysDCAサイクルへの改変

 品質改善サイクルの著名なものの一つに、SDCAサイクルがあります。
 SDCAサイクルは、改善手段として標準化を活用するための改善サイクルです。PDCAサイクルの応用例の一つです。次の4つのフェーズを繰り返して改善します。

  • Standardize(標準化)。プロセス化、手順化で、良い方法を標準化する
  • Do(実行)。標準化された手順に従って活動を進める
  • Check(評価)。標準が適切に遂行されているか、標準が効果を発揮しているか評価する
  • Act(改善)。評価結果に基づいて改善を実施する

 SDCAサイクルは、主にハードウェアの開発・製造で活用されます。ランダム故障を減らし、品質のバラツキを抑えるために、標準化を活用します。

SDCAサイクル(SysDCAサイクル)

 モダンなソフトウェア開発の場合、前述のSDCAサイクルの標準化は、仕組み化に改変した方が都合が良いと感じます。
 ここでいう仕組み化は、プロセスによる仕組み化、体制による仕組み化、自動化、ツールの支援確保で実現します。例えば手作業を、自動化の仕組み(例えばCI/CDデプロイメントパイプライン)に移管して、開発の正確性や効率性を高める、といったことを行います。標準化との違いは、次の3点になります。

  • 目的を標準化に限定せず、活動をよりよくする仕組み全般を整備して労力を減らすことを目指す
  • 仕組みで手作業をなくすことも奨励する
  • 目的だけでなく、手段(自動化技術やツールチェーンの構築など)の蓄積・整備にもフォーカスをあてる

 仕組み化への改変の理由は、手順書・ガイドラインを文書化して作業者を従わせるイメージより、上記のように手段の充実で楽をするイメージの方が、今のソフトウェア開発で好まれますし、成果も引き出しやすいためです。

 そのため、現在のソフトウェア開発では、SDCAサイクルのSをSystematize(仕組み化)に置き換えたSDCAサイクル(あるいはSysDCAサイクル)の方が、より適切だと考えます。
 このSDCA(SysDCA)サイクルは、次のフェーズを繰り返して、仕組みの蓄積・整備を行う形になります。

  • Systematize(仕組み化)。プロセス、体制、開発インフラ、ツール支援で、活動をよりよくする仕組みを構築・改良する
  • Do(実行)。仕組みのサポートを受けながら活動を進める
  • Check(評価)。活動が適切に実行されているか、仕組みが効果を発揮しているか評価する
  • Act(改善)。評価結果に基づいて改善を実施する

生成AIでテストエンジニア/QAエンジニアのプログラミング機会が失われる問題

 自分はここ10年ほどテストエンジニア(SET含む)、QAエンジニアとして働いているのですが、そういった役割でも結構プログラミング機会があります。例えば次のようなものです:

  • 統合テストやEnd to Endテストの自動テストの実装
  • 組込み開発の治具の開発
  • 中大規模なテスト自動化システムの開発
  • CI/CDデプロイメントパイプラインの実装(パイプライン定義だけでなく、周辺のビルドスクリプトや自動化処理の実装含む)
  • QC/QA活動でのデータのモニタリング/分析
  • QC/QC活動に絡む開発協力(プロダクトの監視設計やユニットテスト実装など)

 これらは、簡単な雑用コードの実装レベルに閉じず、本格的な開発も含まれます。こうした開発機会は、テスト・QAの仕事に移る前の開発者時代の技術力を維持したり、テスト・QAエンジニアの業務の中でも開発力を開拓するのに有効でした。

 ただこれに関して懸念を感じているのが、上記のようなテスト/QAエンジニアが手掛けるようなプログラミングの多くが、最近流行の生成AIでカバーできてしまうことです。

 例えばCI/CDツールのデプロイメントパイプラインのコードや簡単な自動化処理は、GitHub Copilotのようなプログラミング特化の生成AIに限らず、ChatGPTのような汎用のものでも少なからず生成できてしまいます。IaCの設定ファイルといったものも生成します。自動テストコードも、テスト分析・設計の全自動化はできないものの、テストコードのスケルトン、セットアップ処理、たたき台のサンプルテストコードを生成してくれます。

 テスト分析など高度な作業、開発現場独自の条件への適応といったもの対応できないほか、ハルシネーション問題もあるため、生成AIを活用しても一定の人間の作業は残ります。ただ、プログラミングの学習機会は質量ともに結構小さくなります。
 その帰結として、生成AIを活用するほど、日頃の業務内容だけで開発力を維持したり開拓したりすることが難しくなる傾向が強まると感じています。

どうしたらいいか

 生成AI時代では、テスト/QAエンジニアはAIが対応できない領域(テスト分析や顧客価値の追及など)にフォーカスすべきという声をちらほら聞きます。
 しかし個人的に、そうした領域だけでなく、テスト/QAエンジニアであっても良い仕事をするために開発力・プログラミング力は必要だと感じています。そのため、生成AI活用においては、生成AIに作業を投げるにしてもそこで使用される基礎技術はしっかり身に着ける、積極的に開発業務に参画して開発経験を積むといった心がけが求められるのではと感じています。

コードの理解しやすさの指標:コグニティブ複雑度について

 コードの読みやすさ、保守しやすさの指標の一つに、コグニティブ複雑度(認知的複雑度、Cognitive Complexity)があります。計測手段が充実してきていることもあり、今回はこのコグニティブ複雑度の概要と、計測方法についてまとめます。

コグニティブ複雑度とは

 コグニティブ複雑度とは、ソースコードの理解しにくさ・複雑さを示すメトリクスです。

{Cognitive Complexity} a new way of measuring understandability

 ソースコードの理解しにくさ・複雑さを示すメトリクスとしてはサイクロマティック複雑度が著名です。コグニティブ複雑度は、このサイクロマティック複雑度の以下の問題を改善するために考案された指標になります。

  • サイクロマティック複雑度はFortran時代に生み出された指標であり、例外処理やラムダといった現代的な言語の構文を考慮できていない
  • サイクロマティック複雑度はメソッド単位の計測でしか有用でない。クラス単位・モジュール単位では理解しにくさと相関性が弱くなる

コグニティブ複雑度の計算ルール

 コグニティブ複雑度の考え方は以下の通りです。

1. Ignore structures that allow multiple statements to be readably shorthanded into one
2. Increment (add one) for each break in the linear flow of the code
3. Increment when flow-breaking structures are nested

https://www.sonarsource.com/docs/CognitiveComplexity.pdf より抜粋

前後の文も考慮した意訳:

  1. 複数のステートメントを1つのステートメントに短縮して読みやすくする言語記法は評価から除外する。
  2. コードの直線的な流れを乱すごとに、複雑度に1を加える。
  3. コードの流れを中断するネストが行われるごとに、ネストのタイプに応じた複雑度を加える。

 以下、それぞれの考え方について解説します。

複数のステートメントを1つのステートメントに短縮して読みやすくする言語記法は評価から除外する

 開発言語によっては、条件式や複数のステートメントを簡潔な1ステートメントにまとめる文法を提供しています。例えばぼっち演算子やnull条件演算子といったものです。複雑度を上げるコードを、そうした記法で簡潔化すると、複雑度を下げる、というルールになります。

 例えば次のようなC#のコードを例にとります。

var label = "";
if (item == null)
  label = null;
else
  label = item.label;

 このコードのコグニティブ複雑度の増加分は、後述するif文にかかわるルールにより+1になります。
 このコードは、null条件演算子を使って以下のように簡潔に書くことができます。

var label = item?.label;

 本ルールは、上記の簡潔化した書き方ならばコグニティブ複雑度の増加分を0とする、というものです。言い換えると、前のif文を使ったコードを、null条件演算子で簡潔化すると、複雑度を減らします。

コードの直線的な流れを乱すごとに、複雑度に1を加える

 次の構文があると、理解に要する労力が増えると判断し、コグニティブ複雑度を1増やします:

  • for、whileといったループ構造
  • if文といった条件分岐構造
  • 論理演算子による複合条件
  • 例外処理のcatch構造
  • goto構文

 また、次の実装もコードの直線的な流れを乱すとし、コグニティブ複雑度を増やします:

 なおswitch構文の導入や論理演算子の記述の工夫で理解しやすさを上げると、その理解しやすさに応じて、複雑度の加算を減免するルールを設けています。詳細は https://www.sonarsource.com/docs/CognitiveComplexity.pdf を参照ください。

コードの流れを中断するネストが行われるごとに、ネストのタイプに応じた複雑度を加える

 ネストでコードの直線的な流れを崩すと、ネストの理解しにくさに応じた複雑度を加えます。「ネストの理解しにくさ」ですが、例えばネストが深くなるほど、加える複雑度を増やします。
 例えば以下のコードの場合を考えます:

if (condition1) { // +1 
  for (int i = 0; i < condition2; i++) { // +2 
    while (condition3) { … } // +3 
}

 最初の「if (condition1)」でネストが増えるため、複雑度に1を加えます。
 次の「for (int i = 0; i < condition2; i++)」は、ネストが深くなる分、複雑度に2を加えます。
 次の「while (condition3)」は、ネストがさらに深くなる分、複雑度に3を加えます。

コグニティブ複雑度の計測例

 コグニティブ複雑度は、サイクロマティック複雑度と比べ、理解しやすさ・読みやすさをより直感的に反映したものになります。
 例えば次のPythonコードのコグニティブ複雑度をflake8-cognitive-complexityを使って算出します。

# match文を使った構文
def sample_method(hoge):
    match hoge:
        case 1:
            print('1')
        case 2:
            print('2')
        case _:
            print('no match')
# if文を使った構文
def sample_method(hoge):
    if (hoge == 1):
        print('1')
    elif (hoge == 2):
        print('2')
    else:
        print('no match')

 前者のコグニティブ複雑度は1、後者のコグニティブ複雑度は3になります。
 if文、elif文は条件式になんでも記述できる一方で、match文(他言語でのswitch文)は指定された値がマッチするかのみしか記述できない点で、理解しやすさ・読みやすいさはmatch文で書いた方が優れています。コグニティブ複雑度の結果は、その感覚と一致するようになっています。

QAエンジニアの定義と分類

 「品質保証(QA)とは。定義の三大流派と定義揺れの弊害 - 千里霧中」で触れたとおり、QAの定義は日本国内では曖昧化しています。それに伴って、QAエンジニアの定義も曖昧化している状態です。曖昧化しているポイントは主に以下の二点です。

  • 「品質保証(QA)」の定義が曖昧
  • チームとの関係性(開発チームに入るか、第三者検証に様に独立性を保つか等)が曖昧

 こうしたQAエンジニアの定義のブレ・曖昧さは、完全内輪向けならば許容される場合があります。
 ただ、外部から知見を取り込んだり、外部から人材を募集したりと、外部組織と接点を持つと支障が出ます。例えば「QAエンジニアを募集」と人材要件に書いても、読み手によって何をする職種なのかの理解がばらばらになる恐れがあります。
 そこで今回は、QAエンジニアを分類することで、自分なりにQAエンジニアの定義を具体化したいと思います。

概要

 「担当する業務の種類」「開発チームとのかかわり方」の二軸でQAエンジニアを整理します。

担当する責務の種類に基づいたQAエンジニアの定義

 次のように定義します。これは択一選択ではなく、複数選択する場合もありえます。

監査型QAエンジニア

 ISO 9000など、国際規格の品質保証(QA)の定義に従った役割です。
 具体的に、QMSの立証を担当するエンジニアです。品質管理活動から離れ、QMSの監査、プロセスの監査、プロセス運用の監査、プロセス志向での開発是正を行うのが、主な業務になります。「テストと品質保証は同じではない。テストは品質コントロール(QC)の形式の 1 つである」(JSTQB FL Ver. 2024V4.0)の通り、テスト活動は実施しません。

QAエンジニア(顧客満足型QAエンジニア)

 飯塚悦功氏、石川馨氏らの著作や、JISハンドブックなど、日本国内の製造業で発展した品質保証の定義に従った役割です。顧客満足の実現を責務とします。具体的な業務として、以下を実施します。

  • 顧客満足の達成を確認するテストを担当する
  • インシデント管理、顧客満足を観点にした要件定義支援や開発成果物レビューといった、テスト以外の顧客満足にかかわる業務を担当する

 監査型QAエンジニアとの違いとして、テスト活動を担当するほか、プロダクト志向の開発是正も取り組みます。テストエンジニアとの違いとして、テスト以外の顧客満足にかかわる業務も担当します。

テストエンジニア(テスト特化QAエンジニア)

 テスト活動専任の役割です。
 特定のテストレベル、テストタイプを担当します。テスト以外の活動は責務外です。SET、テスト自動化エンジニアなど、さらに詳細なテスト役割に細分化できます。顧客満足から離れたテストを扱う場合もあります(例えば旧来の定義でいうQAテストに対比される開発者テスト)。

フェーズゲート型QAエンジニア

 リリース判定、出荷判定、工程移行といった品質ゲートの判定に特化した業務を担当します。具体的には、リリース判定に必要な情報の収集(各種テスト活動結果や品質メトリクス等)と評価、リリース判定テストの活動を担当します。

QCエンジニア

 プロジェクトの品質管理に特化した役割です。各開発活動の成果物品質のモニタリングとコントロールを担当します。PMOであることが多いです。

上記の分類についての留意点

 上記の分類はMECE(漏れなくダブりなく)ではありません。「QAエンジニア(顧客満足型QAエンジニア)」は、「監査型QAエンジニア」「テストエンジニア」「フェーズゲート型QAエンジニア」の役割に部分的に重なっています。

開発チームとの関わり方に基づいた定義

 QAエンジニアの定義とは別に、開発チームとの関わり方を以下のように整理します。

チーム一体スタイル

 開発チームの一員としてQA業務を担当します。例えばスクラム開発ならば、スクラムメンバーとしてQA業務を推進します。

独立スタイル

 特定のプロジェクトのために、開発チームから独立してQA業務を担当します。例えばスクラム開発ならば、スクラムチームから離れ独立性を持った妥当性確認などを担当します。

組織横断スタイル

 特定の開発チームやプロジェクトから離れ、組織横断でQA活動を推進します。

「担当する責務の種類に基づいた定義」「開発チームとの関わり方に基づいた定義」の組み合わせによる整理

 上記の二つの整理は組み合わせることで、包括的にQAエンジニアを整理できるようになります。
 例えば監査型QAエンジニア、フェーズゲート型QAエンジニア、QCエンジニアは、独立スタイルあるいは組織横断スタイルから選択できます。QAエンジニア、テストエンジニアは、チーム一体スタイルか、独立スタイルのどちらでも選べる余地があります。そこで「チーム一体スタイルのテストエンジニア」などと整理することで、QAエンジニアの役割を明確化できます。

個の品質特性と群(集合)の品質特性(ISO/IEC/IEEE 29148を例として)

 品質特性は要求定義やテスト分析、品質評価など、様々な場面で使われる考え方です。この品質特性についてですが、同じ対象に適用する場合でも「全体に適用する場合」と「全体を構成する個々の要素に適用する場合」で様変わりすることがある点、注意が必要です。

ISO/IEC/IEEE 29148 での要求が備えるべき特性

 例えば、要求エンジニアリングの標準規格であるISO/IEC/IEEE 29148:2018では、要求の記述が備えるべき特性をリストアップしているのですが、そこでは個々の要求事項が備えるべき特性と、それら総体の要求の群・集合が備えるべき特性を明確に区別しています。

  • 個々の要求事項が備えるべき特性
    • 必要である(Necessary)。コンセプトやニーズ、要求源を満たすために必須である。欠落すると、埋め合わせ不可能な欠陥を生じさせる。
    • 適切である(Appropriate)。要求の方向性や詳細度が適切である。
    • 単独である(Singular)。単一の機能や特性、制約についてのみ記載している。
    • 正当である(Correct)。要求源が求めるものを正確に記載している。
    • 曖昧性がない(Unambiguous)。要求は一意に解釈されるように記載されている。単純かつ理解しやすいように記載されている。
    • 完全である(Complete)。要求を理解するために他の情報を必要としない。要求源を満たすために必要な情報を十分に説明している。
    • 実現可能である(Feasible)。制約(コスト、スケジュール、技術、法律、倫理)内で、許容可能なリスクで要求を実現できる。
    • 検証可能である(Verifiable)。システムが要求を満たすことを検証できる記述になっている。
    • 適合している(Comforming)。標準やスタイルガイドといった規範に準拠している。

 

  • 要求総体の群(集合)が備えるべき特性
    • 完全である(Complete)。適切な抽象度で、対象の機能、非機能、特性、制約などの情報を十分に説明できる。
    • 一貫性がある(Consistent)。個々の要求に競合や重複がない。用語や言語が一貫している。
    • 実現可能である(Feasible)。制約内で、許容可能なリスクで総体を実現できる。
    • 包括的である(Comprehensible)。対象に期待される内容やシステムでの関係性が明確に記載されている。
    • 妥当性確認可能である(Able to be validated)。制約(コスト、スケジュール、技術、法律、倫理)内で、許容可能なリスクで、要求が対象の目的、ステークホルダの期待、リスク、コンセプトを達成できていることを確認できる。

 上記の通り、同じ要求の品質特性が対象でも、構成要素と総体で特性が変化しています。例えば総体では、構成要素間で競合や重複がないこと、総体として妥当であることを確認できることといった特性が出現します。
 また同じ名前の特性でも、構成要素と総体で意味づけが変化します。例えば「完全である」は、個々の要素では他の情報を必要としないといったレベルの特性である一方、全体の集合では、総体として十分であるといった特性になります。

テストが備えるべき特性

 最近はソフトウェアテストの品質特性もちらほら議論されるようになっているのですが、そこでも個と群(集合)で、備えるべき特性が変わる点、注意が必要です。
 例えば「追跡可能である」という特性は、個のテストケース、総体のテストそれぞれで求められる特性ですが、個の場合は「テスト設計の根拠が追跡可能である」という特性になる一方で、総体の場合は「テストベースとテストが相互に漏れなく追跡可能である」といった特性になります。また例えば「一貫性がある」は、ISO/IEC/IEEE 29148 と同じく、総体としてのテストには適用できる特性ですが、個々のテストケースには適用されません。
 テストの品質特性、品質モデルを考える際は、こうした構成要素と総体の違いを考慮する必要があります。

2024年のふりかえり

登壇

 講演に苦手意識・下手さを感じているため、登壇は一貫して受け身でお声がけ頂いた場合のみ対応しているのですが、それでも今年は様々な機会を頂きました。
 その中で印象的だったイベントが2つあります。1つは開発生産性カンファレンスで、申込者数が4桁を超えていてかなり緊張した記憶があります。もう1つは「井芹さんが語る!」のイベントです。自分はこれまでテストネタで講演してきましたが、現職がQAエンジニアということで、QA特化でテストの話題を極力避けたコンテンツに方向転換しました。いずれも大変好評なアンケート・フィードバックを頂き勇気を頂きました。機会を作っていただいたFindy様ありがとうございました。

Hiroki Iseri (@goyoki) on Speaker Deck

執筆

 今年はこれまでずっと読者として触れていたSoftware Designに寄稿する機会を頂き感慨深かったです。

 また、今年はあるブログの記事をきっかけに書籍を執筆する機会を頂き、その執筆に没頭していました。原稿は7月に完成し提出済みとなっています。来年夏ごろ出版見込みです。なお自分が執筆期間の見積もりを過小評価したせいで、執筆作業はかなり厳しい作業となり反省しています。また多数の方にレビューいただいたのですが、自分の技術的誤解・誤りを複数指摘いただき、自分の浅学さを痛感する良い機会になりました。ご協力いただいた方々ありがとうございました。

仕事

 前半はQAチームリーダ、後半はQA/テスト テックリードの仕事に従事していました。とんでもない規模のプロジェクトで、落ち着かない日々が続く一年でした。暗中模索の状態で、身の振り方に悩んでいます。

趣味・プライベート

 家族も大変な時期が多く、落ち着かない一年でした。

 趣味として、料理が癒しになりました。今は家中華(https://s.magazineworld.jp/feature/post/2801/)のレシピの実践にはまっています。

 また前々からの趣味の鳥写真については、上記の執筆や仕事からのリフレッシュということで、暇を見つけては関東圏で撮影を行っていました。今年は水鳥を多く撮れました。 https://sites.google.com/site/hirokiiseri2/home/photo のサイトで撮れた鳥の種類をまとめているのですが150種類を超えました。これは入門者レベルの数字らしいので来年はさらに伸ばしたいです。