« 2022年11月 | トップページ | 2023年1月 »

2022年12月

2022/12/25

JavaGold SE11の感想

JavaGold SE11を無事に取得できた。
試験勉強を通じて、有意義な経験を得られたと思う。
ラフなメモ書き。
間違っていたら後で直す。

【参考】
Java歴13年がJava Gold(SE 11)を受けてみた - yucatio@システムエンジニア

JavaGoldSE8に受かったので(前編) - Qiita

JavaGoldSE8に受かったので(中編) - Qiita

JavaGoldSE8に受かったので(後編) - Qiita

JavaSilverの感想~Javaはオブジェクト指向と関数型言語の2つの性格を持つ: プログラマの思索

Javaはなぜ関数型言語になろうとしているのか: プログラマの思索

【1】受験のきっかけ
コロナ禍がずっと続いて、リモートワークになりプライベートでも対面の付き合いがほとんどなくなって、余った時間を有効活用したい。
過去にJavaで開発していた頃はせいぜいJava5くらいで、それ以降のバージョンは完全に理解できていなかったので、ラムダ式等をきちんと習得したい。
15年以上前にSunのJava試験を受けて落ちたのでリベンジしたい。

【2】勉強方法
オラクル認定資格教科書 Javaプログラマ Gold SE11(試験番号1Z0-816)」(通称、紫本)と『徹底攻略Java SE 11 Gold問題集[1Z0-816]対応』(通称、黒本)を最終的に5回転以上回した。
黒本だけでは理解できず、紫本を5回転以上回して慣れる必要があった。
なお『徹底攻略Java SE 11 Gold問題集[1Z0-816]対応』の「総仕上げ問題」は実際の試験問題に似ているのでやるべき。

【3】JavaSilverは「Javaはオブジェクト指向である」ことを設計でもコーディングでも理解できれば合格できる。
しかし、JavaGoldは「Javaは関数型言語である」こと、特にラムダ式を使って設計でもコーディングでも理解する必要があった。
頻出論点は、ストリームAPIと関数型インターフェイス、モジュールシステムだったと思う。
僕には正直難しかった。
理由は、オブジェクト指向プログラミングはUMLによるモデリングを通じて経験していたが、関数型言語プログラミングのお作法もその概念も理解していなかったから。
「OptionalはMaybeモナド、ストリームAPIはMapReduceでありリストモナド、入出力APIはIOモナド」ということを最終的に理解する必要があるのではと思った。

【4】 第1章 クラスとインタフェース

なぜ内部クラス(inner class)が必要なのか?
2つのクラスソースファルを1つにまとめる必要がある時に使う。

内部クラス→static内部クラス→ローカルクラス→匿名クラス→ラムダ式 の順に昇華されていく。
よって、ラムダ式の発端は内部クラスにある。

インナークラスのインスタンス化には、エンクロージングクラスのインスタンス化が必要である。
つまり、無意味なインスタンス化のロジックが発生する。
そこで、インナークラスをstaticインナークラスにすれば解決できる。
staticインナークラスならば、エンクロージングクラスのインスタンス化無しで、インナークラスのインスタンス化ができる。
staticインナークラスにすれば、staticなインナークラスのフィールドやメソッドをエンクロージングクラスでそのまま参照できる。

staticなインナークラスでは、フィールドやメソッドの参照はコンパイルエラーになる。
一方、staticでないインナークラスでは、フィールドやメソッドを参照できる。

Effective Javaでは、「内部クラスにはstaticを付けるべき」というプラクティスがある。

内部クラスにstaticを付けると、内部クラスから外部クラス(エンクロージングクラスとも言う)のフィールド変数にアクセスできなくなる。
つまり、スコープが小さくなる。

ローカルクラスから参照するローカル変数は、実質finalが必須。
実質finalでなければコンパイルエラーになる。
ローカルクラスから参照するローカル変数を、ローカルクラスの後で変更するとコンパイルエラーになる。
この仕様は関数型言語のイミュータブルな性質に似ているので、そのような仕様にJavaが合わせたのではないか。

一方、RubyやPythonのクロージャでは、ローカル変数を変更しても問題なく動く。
Rubyのブロックはメソッドによる手続きブロックとは異なって、ブロックの外側で定義されたローカル変数をブロック内で参照・変更できるという性質を有する。ただブロック内で定義された変数はその外側で参照できない。

無名クラスは、メソッド内に無名のインナークラスを定義するので、ラムダ式。と実質同じ。
Effective Javaでは、「無名クラスよりもラムダを選ぶ」プラクティスがある。
無名クラスは冗長な記述であり、ラムダ式の方が簡潔に書けるからだ。

匿名クラスにコンストラクタは定義できない。
しかしインスタンス初期化子を使えば、似たような処理を行うことが出来る。

インターフェイスのdefault/privateメソッドは、Scalaのtrait, Rubyのmoduleと同じ。
ただし、インターフェイスを継承した2階層下のクラスや2階層下のインターフェイスでは使えない。
インターフェイスのstaticメソッドは、継承直下の子クラスのみ利用できる。
privateメソッドはdefaultメソッドから呼び出されることを前提としている。
インターフェイスは型の提供が目的であり、メソッドの実装は基本は実装クラスが提供する。
インターフェイスのstaticメソッドとラムダ式のおかげで、Factroyクラスが不要になった。

なぜJavaのインターフェイスがのstatic/default/privateメソッドを必要とするのか、理由が分からなかった。
たぶん、Scalaのtrait, Rubyのmoduleを真似て、共通ロジックをわざわざインスタンス化して利用する面倒な手続きを減らしたい意図があるのだろう。

Enumはシングルトンクラス。
Effective Javaでは、Enumに独自に新規メソッドを実装するプラクティスがある。

Javaのenum型はシングルトンクラスみたいだ: プログラマの思索

Effective Java 第3版 第6章enumとアノテーション - Qiitaでは、int型をEnu型で書くべき、というプラクティスは理解できる。
しかし、「拡張可能なenumをインタフェースで模倣する」プラクティスのように、通貨クラスに「+」「-」のようなメソッドを独自で実装するという発想はなかった。
こういう考え方がドメイン駆動設計の値オブジェクトの実装に役立つのだろう。

【5】 第2章 関数型インタフェースとラムダ式

Javaのラムダ式は、関数型インフェーエイスの宣言とインスタンスの生成を同時に行う文法とみなす。
つまり、ラムダ式=関数型インターフェイス(抽象メソッド1個だけ)+インスタンス生成。

ラムダ式を使えば、Factroyクラスは不要になる。
つまり、DIを実現するために、Factoryパターンを使い、その実装にラムダ式を使う。
ラムダ式を使えば、Strategyパターンを短く書ける。
ラムダ式をメソッドチェーンで書けば、Builderパターンを短く書ける。

Javaのラムダ式のローカル変数は、スコープ外では実質finalとして扱われる。
ローカル変数の値を更新するとコンパイルエラーになる。
この性質は関数型言語の特徴に似ていると思う。

@FunctionalInterfaceを付けたインターフェイスは、自動的に関数型インターフェースとして使用できるが、抽象メソッドは1個だけ限る。
他言語では関数1個で定義できるが、Javaは必ず関数をclassで囲む必要があるので、関数型IFで定義する必要がある。

Javaの関数型インターフェースの抽象メソッドで注意すべき点は、java.lang.Objectのメソッドはカウントされない。
たとえば、public abstract String toString()があれば、抽象メソッドにカウントしない。

ラムダ式を使うと何が嬉しいのか?
1.抽象メソッドを実装したクラスを最小のコード量で実装できる
2.抽象メソッドの実装と、メソッドを使うところを一つにできる

関数型インターフェイスには1つしか抽象メソッドがないので、戻り値・引数の型と順番を、関数型インターフェイスの型からJavaコンパイラが推測できる。この仕組みが型推論なわけだ。
だから、Haskellのような関数型言語はコンパイラを作りやすいという理由は、型推論が強力な特徴があるからだろう。

通常パターン;
* Function, XXXFunction :apply()
* => 戻り値:R
* Supplier:get()
* => 戻り値:T
* Consumer, XXXConsumer :accept()
* => 戻り値:void
* Predicate, XXXPredicate :test()
* => 戻り値:boolean

特殊パターン:
* ToXXXFuntion:applyAsXXX()
* ToIntFunctionならapplyAsInt()のような感じ。
* XXXSupplierの形だけ注意! => getAsXXX()
* IntSupplier:getAsInt()
* DoubleSupplier:getAsDouble()
* LongSupplier::getAsLong()

UnaryOperator、BinaryOperator=引数と戻り値の型が同じ。
引数と戻り値の型が同じなので、Gnericsは1つで良い。

【6】 第3章 並列処理

並行処理(concurrent):処理を切り替えて同時に動いているように見せる。Threadクラスと同じ。
並列処理(parallel):複数のコアで同時に処理を行う。ストリームAPIでparallelメソッドを使う。

Executor によって処理されるタスクの状態遷移図
created(タスク生成) 
→ submitted(キューにタスクを登録) 
→ started(タスクのrun実行)
→ completed(タスクの終了)

Thread.run()がキューに登録されて、Thread.start()で、run()が実行される。
キューに登録されたRunnable.run()が実行される。

なぜ、並列処理はラムダ式や匿名クラスを使うのか?
スレッドのタスクは、関数型インターフェイスRunnnableのラムダ式で実装されるから。

Future=スレッドを生成したメソッドが、新しく作ったスレッドの結果を保持する。
submitの戻り値=Future => Future.get()で取得できる。
list.add(services.submit( XX -> XX))を実行できる。
一方、executeの戻り値=無し。

Runnable.run()はvoidなので、何も返さないから、Future.get()はNULLを返す。
Callable.run()は戻り値があるので、Future.get()は結果を返す。

Future fut = ex.submit( () -> {処理}, 0); は、第2引数0を返す。

Callableで定義したタスクで例外が発生した時、ExecutionExceptionでキャッチする。

CyclicBarrier=スレッドを待機させる。
java.util.concurrent.CyclicBarrierクラスを利用すると、複数のスレッドが特定のポイントまで到達するのを待機させることができる。
CyclicBarrier=複数のスレッドが特定のポイントまで到達するのを待機できるようにする同期化支援機能を持つスレッドクラス。
スレッドN本目を通過すると、await()の待機は解除される。

* バリアー:複数スレッドの待ち合わせポイント
* バリアーアクション:待ち合わせ後に実行される処理

マルチスレッドで扱うクラスのフィールドにvolatile修飾子を付けると、キャッシュされなくなる。
これにより並列処理時にどちらかがキャッシュを読み込むことによる不整合をなくせる。

【7】 第4章 ストリームAPI

ストリームAPIとは何なのか?
一言で言えば、JavaのMapReduce用APIと思えばいい。
大量データをリストで引数として設置して、Mapで処理させて並列処理できるようにばらして、並列処理の結果をreduceで1つにまとめて戻り値に返す。

実際の問題では引っ掛けパターンが多い。

ラムダ式のローカル変数は実質finalなのに、2回代入している
ただし、ローカル変数が配列ならポインタ参照なので、2回代入はOK。
たとえば、ArrayListやint[]の場合。

ラムダ式を{}で書いたのに「;」なし。

ラムダ式の中で、varの引数はコンパイルOK。
つまり、ラムダ式内の処理の引数は型推論される。
ただし、varはメソッド引数やメンバ変数に使うとコンパイルエラーになる。

Stream<型>のようなGnericsで引っ掛けるパターンが多い。

* map(T -> R)とflatMap(T -> Stream)
* mapToInt()とmapToObject()
* mapToInt(ToIntFunction)とmap(T, R)
* pararell(Stream --> Stream)とpararellStream(Collection --> Stream)
* reduce(BinaryOperator --> Optional)とreduce(DoubleBinaryOperator -->DoubleStream)

終端操作を2回実行すると実行時に例外が発生する。
例:anyMatch, count, reduce, forEach, collect

中間操作だけで終わると、値は返却されない
例:peek

終端操作countだけ実行しても出力されない。System.out.println(count)が別途必要。

List.stream().pararell() <=> List.pararellStream()は同じ。

boxed()は、XXStream -> Streamへ変換する。
逆の操作は、Stream --> mapToInt(IntToIntFunction)--> IntStream。

reduce().orElse()でint型、double型を返す。

Javaのsumはreduceで置き換えられる: プログラマの思索

Javaのreduceの使い方は2種類ある: プログラマの思索

モナドとは「メソッド内の副作用の存在を戻り値の型で表現する」ためのデザインパターン。
「関数に副作用がある」ことを「戻り値の型」で表現している。
Genericsを使って、戻り値の型を増やしている。

なぜ関数型IFではGenericsが頻繁に使われるのか?
理由は、関数の副作用を戻り値の型で表現するために、Genericsを使って戻り値の方の個数を増やしているからと考える。

OptionalはMaybeモナド。
Optionalの語源:値がないかもしれない => オプションの値を作りましょう。

StreamはListモナド。
MapReduceの戻り値はOptionalを返す。
それにより、Nullの戻り値はなく、ヌルポを防げる。

IOはIOモナド。
入出力処理における副作用は、戻り値の型をGenericsで表現する。

StreamAPIはメソッドチェーンで書かれるので、処理の途中でどのように戻り値の型が変換されて遷移するのかわかりにくい。
そこで、StreamAPIはバラして考えるとよい。
Eclipseの「ローカル変数の抽出」を使って、メソッドチェーンをばらすといい。

【8】 第5章 入出力

java.ioパッケージは古い機能。
たとえば、Fileクラスは、ディレクトリやファイルへのパスを扱うだけで、ファイル自身を表すのではない。

java.nioパッケージは新しい機能なので、痒い所に手が届くようになっている。
nio.Filesクラスは新しい入出力API => 同じパスに同じファイルがあれば作成時に例外を発生させて検知してくれる。
NoSuchFileException - ファイルが存在しない場合
DirectoryNotEmptyException - ファイルがディレクトリで、ディレクトリが空でないために削除できなかった場合

FileクラスとFilesクラスの違いは覚える。
Fileクラス提供のisXXXメソッドは、引数を取りません。
* 例:File.isDirectory() : boolean
* 例:File.isFile() : boolean

Fileクラス提供のメソッドは、renameTo(dest)以外はほとんど引数を取りません。
* 例:dirFile.mkdir()
* 例:dirFile.mkdirs()
* 例:file.renameTo?(File dest)
* 例:dirFile.listFiles()
* 例:file.getAbsolutePath()
* 例:file.toPath()
* => new File("ファイル名").toPath()の形式でPathオブジェクトを生成する

Filesクラス提供のメソッドは、全て引数を取ります
これ覚えとくだけで事前にコンパイルエラーかわかるようになった。
* 例:Files.isDirectory(Path dirPath)
* 例:Files.deleteIfExists(Path path) =>ファイルやディレクトリが存在している場合だけ削除する
* 例:Files.list(Path dirpath): Stream =>ディレクトリ内の全てのパスを表示
* 例:Files.find(開始パス、深さ、BiPredicateオブジェクト、オプション): Stream
* 例:Files.isSameFile(path1 : Path, path2 : Path) : boolean : path1とpath2が同じか否か
* Files.move?(Path source, Path target, CopyOption... options)
* Files.copy?(Path source, Path target, CopyOption... options)

Files.walk(), Files.find()は引数を覚えるのが重要。
Filesクラスのwalk()は再帰的にパス情報を取ってくる。
Files.walk(開始パス、深さ、オプション):サブディレクトリまで展開したパスを再帰的に表示
指定された開始ファイルをルートとするファイル・ツリーを参照することで Pathが遅延移入されるStreamを返します。

Filesクラスのfind()は再帰的にパス情報を処理して、判定条件に合致したファイルだけを探す。
Filesクラスのfind()の引数の順番は覚えたほうが良い。
Files.find(開始パス、深さ、BiPredicateオブジェクト、オプション):サブディレクトリまで展開したパスを再帰的に処理して必要なフィルのみ表示
指定された開始ファイルをルートとするファイル・ツリー内でファイルを検索することで Pathが遅延設定されるStreamを返します。

XXのようにStreamがついてなければCharacterストリーム。
XXStreamのようにStreamがついていればByteストリーム。

getDefault()は、FileSystemsクラス, Localeクラスにある。
SystemDefault()はZoneIdクラスにある。

Serializableのtransient修飾子は、シリアライズ対象から外す。
デシリアライズ後はnullになる。
static変数は、シリアライズ対象外になる。
∵static変数はグローバル変数なので値は保持される。

【9】 第6章 JDBCによるデータベース連携

Statement ◇--PreparedStatement, CallableStatement

Statement 単純な実行計画を行いたい時(select * from ... を 1 回だけ など)
PreparedStatement 複数回に渡る実行計画を行いたい時, ? のパラメーター解析を使いたい時
CallableStatement ストアドプロシージャを実行

Statementは静的なSQLを扱い、PreparedStatementはプレースホルダを使った動的SQLを扱えます。
基本は、Statementクラスを用いずにjava.sql.PreparedStatementクラスを使用する。
∵SQLインジェクション対策になる。
∵名前の通り、SQLがDBにキャッシュされるため、繰り返し同じSQL文を発行する場合に処理速度が速くなる。
PreparedStatementはDBMSが理解できるようにSQL文をあらかじめコンパイルするから。

まあ、今ならベタなPreparedStatementで実装することはなく、フレームワークで書くのが普通だろう。

【10】 第7章 汎用とコレクション

Genericsの使い道は2種類しか思いつかない。
ListやMapの型安全。
関数型IFの引数、戻り値の型定義。

Cell と Cellは全く別のクラスになる。
Genericsを使う時に、初心者が間違えやすいらしく、僕も最初はハマった。

Cellにすれば、パラメータに全てのクラスを扱える。
しかし、Cellではパラメータにクラスの制約がないのは不便。
そこで、Cell,Cellを使って、パラメータに使えるクラスの範囲に制約をかける。

一般的に PECS という呪文が存在し、上限付き境界ワイルドカード型をProducerと呼び、下限付き境界ワイルドカード型をConsumerと呼ぶことがあるらしい。
Producer - 値の生成専門
Consumer - 値の受取専門

PECS(Producer extends and Consumer super)とは、Javaのジェネリックスプログラミングにおいて、ジェネリッククラスのメソッドに柔軟性を持たせるための原則である。基本は以下の通り。
メソッドが値を取得するコレクション(Producer)は型にextendsをつける
メソッドで値を設定するコレクション(Consumer)は型にsuperをつける

Javaジェネリクス:PECSって何? - Qiita

java - What is PECS (Producer Extends Consumer Super)? - Stack Overflow

JavaジェネリックスのPECS原則、extendsとsuperの勘所 -- ぺけみさお

Genericsの型推論は、ダイヤモンド演算子<>を記述すると、下記が推論される。
* 変数への代入 =>例: var a = new ArrayList<>();
* メソッドの戻り値 =>例: return new ArrayList<>();
* メソッド呼び出しの引数 =>例: execute(new ArrayList<>());

ComparableとComparatorの違いも注意。

ComparableとComparatorの違いは何か: プログラマの思索

【11】 第8章 アノテーション
【12】 第9章 例外とアサーション
【13】 第10章 ローカライズ
は省略。

【14】 第11章 モジュール・システム

JavaのモジュールシステムでSPIとDIを実現するやり方: プログラマの思索

Javaのモジュールシステムの考え方をまとめてみた: プログラマの思索

Javaのモジュールシステムは複雑性をより増している: プログラマの思索

【15】 第12章 Java SEアプリケーションにおけるセキュアコーディング
は省略。

| | コメント (0)

2022/12/23

現代日本人の弱点はリーダーシップ不足と生産性が著しく低いこと、そしてリスク許容度が著しく低いことだ

採用基準」「生産性」を読み返したらいろんな気付きがあった。
ロジカルでないラフなメモ書き。

【参考】
「採用基準」の感想~日本の根本問題はリーダーシップの総量が不足していること: プログラマの思索

DXとは組織論である: プログラマの思索

Slack導入がDXに繋がる話: プログラマの思索

諸問題を組織論に持っていくのは目的を手段化していないか: プログラマの思索

ITの地殻変動はどこで起きているのか?~今後の課題はソフトウェア事業におけるエージェンシー問題を解決すること: プログラマの思索

失敗の本質―日本軍の組織論的研究の感想: プログラマの思索

プログラマとスクラムが社会実装を変えていく #Findy_GovTech: プログラマの思索

デジタル庁が解くべき課題とITエンジニアの役割の勉強会の感想~CTOの役割とは何ですか?: プログラマの思索

みんなのPython勉強会#65の感想~社会変革の鍵はIT技術者にあるのかもしれない: プログラマの思索

マッキンゼーの報告書「2030 日本デジタル改革」が手厳しい: プログラマの思索

【1】最近の日本のIT業界を見ていると、主に2つの現象が目につく。
一つは、DXに向いた組織を作ろう、という組織論の話。
もう一つは、DXを実現するためにアジャイル開発をもっと積極的に導入して運用しよう、という話。

この2つの話の背景には、2つの問題意識が真因として隠れていると思う。
具体的には、組織論の話題、アジャイル開発の話題の背後には、日本人はリーダーシップが不足していること、日本人は生産性の意識も言動も非常に低いことだ。

【2】今のビジネス界隈では、DXがバズワードだ。
DXを実現するには、既存の業務であれ、新規の事業であれ、ソフトウェアでコスト削減、さらには売上の創出が求められている。
DXを実現するには、ソフトウェア開発者、そしてそれを取り囲む組織という基盤が必要だ。

しかし、DXに関する組織論のテーマはすごくフワフワとしていると思う。
命令指揮系統ではなくフラットに風通しを良くしよう、心理的安全性が担保されるような組織風土を作りましょう、という組織文化の話題が多い。
だがそういう甘い言葉の背後には、今までの組織文化や事業スタイルを捨てて、新しい事業や新しい組織の関係を作ろうとする原動力が必要なのに、たくさんの壁にぶち当たる怖さが説明されていないように思える。
だから、何となく、現状を批判するだけで何も変わらない、という現象が出ているように思える。

実際は、既存の組織風土を変えるのは、今までの人間関係や組織との関係を変えることであり、自らリーダーシップを発揮して動いていかなければ何も変わらない。
ものすごく自頭の良い人やできる人に従ってやれば問題解決するわけではない。
自らチェンジ・エージェントになり、たくさんの困難な壁にぶち当たるごとに、一つずつ壁を壊したり乗り越えていくパワーが必要になる。

採用基準」では、問題解決には、問題解決スキルだけでなく、問題解決リーダーシップ(Problem Solving Leadership)が必要と主張している。
たとえば、目の前に起こったいじめの問題に対し、MECEやロジックツリーだと言っても何も変わらないし、実際に解決するように行動して初めて、問題解決の方向に動き出す。
それが本来のリーダーシップ。

すると、このリーダーシップの背後には、「自らリスクを取る」という概念が隠れていることが分かる。
自分の思いどおりに変わらない状況に対し、自分と異なる価値観を持つ人を説得して調整したり、自分が持っていない知識やスキルはそいうう専門スキルを持った人たちに働きかけてチームとして問題解決を図ることが必要になってくる。
そういう行動は、思い通りの結果にならないかもしれないリスクに自らチャレンジすることを求める。

しかし、日本人はリスクを取りたがらないと一般的に言われている。
すべてのリスクを回避してリスクをゼロにすることばかりに専念している。
だから、リスクを取ってリーダーシップを発揮するという行為、選択肢が取りにくい人が日本には多いのだろう。

実際、「採用基準」に記載されている通り、日本人はチームプレーでチームに貢献した成果を問われる経験が極端に少ない。
たとえば、小中高校生なら、受験という行動はその人だけの能力測定試験であり、個人プレーにすぎない。
社会人になっても、プロジェクトリーダーや管理職にならない限り、自分だけの仕事の成果しか問われない。
すると、一般職の日本人は一生、チームでの成果を求められる、というリーダーシップ経験を積まずに終わる人が多い。

そんなリーダーシップ経験のない人は、わがままな振る舞いが多い。
チームで成果を出すためにそれぞれの役割を認識せず、自分が一番成果を出しやすい行動に走ってしまいがちだから。
自分が成果を出しやすい個人プレーに走るのは、自分が苦手な場面に行動するリスクを取らないことにも通じる。

そんなことを考えると、リーダーシップ不足という弱点をごまかして隠すことで、組織論というふわふわしたテーマに流れてしまうのだろう。
そしてリーダーシップ不足という日本人論の問題点は、日本人はリスク許容度が低いことに真因があると思う。

【3】アジャイル開発は20年以上前から提唱されているのに日本ではなかなか導入すらされなかったが、ここ最近になって積極的に取り入れようとする流れが出てきた。

アジャイル開発の本質は一体何なのか?
僕は、アジャイル開発とは時間価値を最優先にしたソフトウェア開発だ、と一言で言えると考える。
WF型開発のように、時間も労力もかけて品質を作り込んで、高品質なソフトウェアをリリースするのではなく、小刻みにいち早くソフトウェアをリリースすることで売上もキャッシュも獲得していく戦略を取る。
1年間で1回のリリースではなく、5回リリースできるなら、5回分のフィードバックが得られて、その分、市場ニーズに合ったソフトウェアへいち早く開発できるようになる。
つまり、リリース頻度が5倍多いなら、ソフトウェアの価値も5倍高まるメリットが生まれる。

では、なぜアジャイル開発は日本で受け入れられなかったのか?
アジャイル開発の源流はトヨタ生産方式と言われていて、日本人にも馴染みがあるのに、なぜアジャイル開発は日本人にフィットしなかったのか?
たぶんその最大の理由は、ソフトウェア開発の生産性が低いことが問題だ、という意識が非常に薄いことだと思う。

官公庁みたいな縦割り組織の日本人は生真面目なタイプが多いので、決められたルールに従う方が重要であり、コストや期間を度外視したり生産性を重視する行動に行きやすい。
また、ソフトウェア開発者派遣のような人月ビジネスでは、たくさんの工数がかかるほど儲かるので、生産性を上げるモチベーションがビジネスモデル上生まれにくい。
今の日本の製造業では生産工程の改善により原価低減による付加価値向上を目指すが、3%の生産性向上よりも30%以上、2倍以上の生産性向上を目指すような、イノベーションを取るような行動が生まれていない。

なぜ米国企業は90年代に蘇ったのか~日本の手の内は完全に読み取られた~V字回復の経営の感想: プログラマの思索

生産性が低い現象が問題だ、と考えて、その問題解決を図ることにより、生産性をさらに大幅に向上させるという正のループを作り出せていない。
特に、3%の生産性向上のようなちょっとした改善ではなく、30%や2倍以上の生産性向上を図ろうとすれば、今までのやり方を捨てて、新たなアイデアを試す、といったリスクを取らなければ実現できないだろう。
しかし、今までリスクを取ったことがない人が、いきなりハイリスクハイリターンの選択肢を取るのは非常に難しいだろう。
なぜならば、仕事でもプライベートでも、そういうハイリスクハイリターンの練習を経験していなければ、実践で試すことは難しいだろうから。

日本人の生産性が著しく低い、という点にも、日本人はリスクを取りたがらない、という現象がその問題の背後に隠れている。

つまり、「ソフトウェア開発のように、時間価値が重要な意味を持つビジネスでは、生産性に比重をおいて時間戦略を取るべきだ」という考え方が日本人も日本企業も受け入れられていないからだ。
その真因には、日本人のリスク許容度が低いこと、土建業界やIT業界に限らず自動車業界においても日本のあちこちの業界で多重請負ビジネスがはびこっていることにあるのだろうと思う。

なぜ米国企業は90年代に蘇ったのか~日本の手の内は完全に読み取られた~V字回復の経営の感想: プログラマの思索

【4】リーダーシップ不足や生産性が著しく低いという現象には、リスクを取らないという日本人の気性が出ているのではないか。
あえてリスクを取ることで、大きなリターンを得る、というハイリスクハイリターンの選択肢を最初から捨てている場合が多いのではないか。

リーダーシップがあり、生産性が高い人は、いろんな問題解決に対してリスク許容度が広いので、かなり大きなリスクを自ら選択することができる。
つまり、リスク対応力とその人の問題解決能力は比例しているのだろうと思う。

【5】組織論やアジャイル開発をテーマにしている人たちは、日本人の弱点である「リーダーシップ不足」「生産性が著しく低い」「リスク許容度が著しく低い」という真因におそらく気づいている。
その真因をいろんな角度から、いろんな言葉で、いろんな手段で解決を試みようとしているのだろうと思う。
そういう観点を持って、今のDXにかかわるテーマを取捨選択して聞いてみたいと思う。

| | コメント (0)

2022/12/17

「Redmineハンドブック」は良い本です

9月に出版された「逆引きでわかる! Redmineハンドブック バージョン5.0対応」を@agilekawabataさんから献本して頂きました。ありがとうございます。
読みどころを書きます。

【1】「Redmineハンドブック」の一番良い点は、こういうことがしたいんだけどRedmineの機能は何だっけ?と疑問に思った時に、逆引きで探せること。

たとえば、期日が切れたチケットを表示したり、放置されたチケットを表示したり、チケット同士を関連付けたり、チケットに先行後続の関係を付けるとか、探す時に便利。
Redmineのようなチケット管理ツールに慣れていれば、すぐに分かるけれど、初心者や慣れていない人にとっては、知らないことがすごくストレスになる。
たぶん、目次や索引から探すようになるだろう。

【2】「Redmineハンドブック」では細かいノウハウが紹介されている点も良いと思う。
たとえば、親子チケットの設定では、大日程だけで概算見積して親チケットの開始日・期日を設定した後、小日程計画で小チケットに細かくブレイクダウンして期間を設定すると、親チケットの開始日・期日が消えてしまう。
なぜなら、Redmineの初期設定では、親チケットの情報は子チケットをロールアップされる仕様だから。
よって、管理画面で、親チケットの開始日・期日を独立させるような設定をする必要がある。

また、大人数のユーザや多数のユーザグループを作った場合、細かくロール設定するのが面倒だ。
そんな場合、JAXAのロール設定の事例の話が「Redmineハンドブックで紹介されている。

CODA チケット管理システム | JSS@JAXA
「CODA: JSS2の運用・ユーザ支援を支えるチケット管理システム: Redmineの事例と利用のヒント」

JAXAのRedmine運用事例の分析~「ロール設定のORルール」と「カスタムフィールド設定のANDルール」: プログラマの思索

JAXAのスーパーコンピュータ活用課でRedmineを使ったチケット管理システムの経験論文: プログラマの思索

【3】「Redmineハンドブック」では、WF型開発やアジャイル開発の運用方法が紹介されている点も良い。
慣れていない人にとっては、やはり実際にどのように運用できるのか、イメージが湧きやすいと思う。

WF型開発では、WBSをチケットに落とし込んで、ガントチャートで管理するのが一般的だ。
ガントチャート画面にイナズマ線が表示される機能も紹介されている。

一方、アジャイル開発では、スプリントはバージョン、タスクはチケットに対応付けて、チケット一覧でスプリントごとにグルーピングしてタスク管理するのが一般的だ。
チケット一覧ではやりにくい場合は、かんばんプラグインを入れて、タスクボードをかんばんで見立てるとよりアジャイルな雰囲気が出てくるだろう。
たとえば、アジャイルウェア社のLycheeかんばんもあるし、フリーでいくつかのプラグインがある。

チケットパネル (Redmineプラグイン) - ファーエンドテクノロジー株式会社

[Redmine] チケットをかんばん表示にするプラグイン | 適当に書き連ねるネタ帳のようなもの

GitHub - happy-se-life/kanban: Kanban plugin for redmine

【4】「Redmineハンドブック」で面白いと思った点は、最終章にGTDのタスク管理が紹介されている点だ。
やっぱりチケット管理はGTDのようなライフハック的なタスク管理のほうが向いていると思う。
Redmineにガントチャートはあるが、タスクをチケットでサクサク登録して更新してCloseしていく方が開発のリズムが生まれる。
すると、粒度の小さいチケットをたくさん作り、バックログとして一列に並べて、上から順にチケットをこなしていったり、溜まったチケットは定期的に棚卸しして作業しやすくする、といった運用方法になってくる。
つまり、自然にGTDのようなタスク管理に近づくわけだ。

さらに「Redmineハンドブック」では、仕事に追われない仕事術 マニャーナの法則 完全版 | マーク・フォースター, 青木 高夫 |本 | 通販 | Amazonをもとにいくつかの役立つ概念を紹介している。

タスクチケットは期日や重要度、優先度、ステータスによって種類がいくつかある。
それらをリストに分類し、リストに名前付けして管理するわけだ。
たとえば、オープンリストとクローズリスト、マスターリストとフィルターリスト、いつややることリスト、ペンディングリストなど。
初歩的なやり方ではあるが、割と使える方法だと思う。

というわけで、手元に1冊あると役立つ本だと思います。

| | コメント (0)

2022/12/13

DDPは品質管理に役立つのか

DDP(Defect Detection Percentage)は品質管理に役立つのか、について考えたことをメモ。
ラフなメモなので、間違っていたら後で直す。

【参考】
資格認定ISTQBはソフトウェア・テストの何を変えたのか? ―― ソフトウェアテストシンポジウム 2013東京(JaSST'13 Tokyo)|Tech Village (テックビレッジ) / CQ出版株式会社

バグ密度・テスト密度に依存しない品質保証への挑戦 | DATA INSIGHT | NTTデータ

コード行数を用いない品質分析技術と開発速度を落とさない品質管理手法の提案~NTTデータ ソフトウェア品質シンポジウム2021

テスト / 品質関連メトリクスまとめ | Test-Hack | 3分で理解するIT/テスト技術

DDP(欠陥検出率)とは何か。 - ソフトウェアの品質を学びまくる2.0

【1】
ソフトウェアテスト標準用語集 (日本語版)によれば、欠陥検出率(Defect Detection Percentage (DDP))の定義が書かれている。

欠陥検出率(Defect Detection Percentage (DDP)): あるテストレベルで見つけた欠陥の数をそのテストレベル、及び、以降のテストレベルで見つけた欠陥の総数で除算した値。escaped defects も参照のこと。

テスト / 品質関連メトリクスまとめ | Test-Hack | 3分で理解するIT/テスト技術では、
欠陥検出率(DDP) =検出バグ数÷当初保有バグ数×100
で紹介されている。

DDPの事例としては、コード行数を用いない品質分析技術と開発速度を落とさない品質管理手法の提案~NTTデータ ソフトウェア品質シンポジウム2021が有名なのだろう。

ただし、DDPをよくよく考えてみると疑問がいくつか出てきた。
上記のPDFでは、単体テストで潜在バグをすべて検出できず、後工程である結合テストで流出した単体テストのバグ数を数えて、結合テストフェーズでDDPを算出している。
その内容を見ると、DDP=9/10=90%なのでそれほど流出しておらず問題ないように見える。

しかし、結合テストフェーズでは、3件のバグのうち1件が単体テストから流出したバグなので、前工程から流出したバグの割合は1/3=33%になる。
よって、結合テストでは本来結合テストで見つけるべきバグよりも、前工程から流出したバグが多く見つかっているのではないか、という疑念が生じる。

また、資格認定ISTQBはソフトウェア・テストの何を変えたのか? ―― ソフトウェアテストシンポジウム 2013東京(JaSST'13 Tokyo)|Tech Village (テックビレッジ) / CQ出版株式会社でもそうだ。
ここでは、アジャイル開発でスプリントごとにDDPを算出している。

スプリント1のDDP=40/(40+10)=80%なので、後のスプリントにあまり流出しておらず、スプリント1は品質が良さそうに見える。
しかし、スプリント2では、バグ45件に対し、10件がスプリント1という前のスプリントのバグが見つかっている。
つまり、スプリント2のうちスプリント1が占めるバグの割合は、10/45=22%にものぼる。
前のスプリントで潰しておくべきバグが後のスプリントでたくさん見つかるということは、今実施中のスプリントで本来見つけるべきバグ検出にリソースを集中することができていない疑念がある。

また、スプリント2のDDP=35/(35+25)=58%なので、かなり品質は悪い。
スプリント3でスプリント2のバグが占める割合は、25/80=31%なので、スプリント2の時よりももっと品質が悪くなっている事実を示している。

【2】DDPが高い数値で品質が高そうに見えるが、実は品質が悪かったという状況はどんな時なのか?

それは、前工程や前のスプリントで多数のバグが見つかったケースに相当するだろう。
つまり、たとえば、前工程や前のスプリントで20件、50件、100件のように多数のバグが見つかった場合、後工程で10件流出したとしても、DDPの値は高めに出るので、一見品質は良さそうに見える。
しかし、元々DDPの分母や分子の数値が大きいので、そう見えるだけ。
後工程で、前工程から流出したバグの割合を見れば、多数のバグが流出しているだろうから、前工程ですべてのバグをすべて潰しきれていないことを示しているだろう。

つまり、DDPは前工程のバグの流出を許さないような前提条件で品質を考えている場合、DDPは品質管理に有効とはいえないだろう。
WF型開発やアジャイル開発でも、後工程がお客様という立場であれば、DDPの指標がたとえ高くても、その妥当性は色々分析してみる必要がある。

【3】そんなことを考えると、品質管理に出てくるメトリクスは1つの観点のKPIに過ぎず、品質という抽象的な成果を評価するには多数のKPIをつなげて1つのストーリーを作る必要が出てくる。
したがって、品質に問題がないという説明で納得させるには、いろんなKPIをつなぎ合わせて試行錯誤して、なんとか妥当性の根拠を示すという手間がかかるわけだ。

プロマネはそういう部分で数多くの苦労をしているので、品質管理や不具合分析は胡散臭いと思っているのかもしれない。

【補足】
@sakaba37さんのコメントを追記しておく。
メトリクスには罠が多い。
さかばさんはTwitterを使っています: 「@akipii 開発中は「いつもと同程度なら、いつもと同じようなテストが実施されたと推定できる」以上のことはわからないように思います。」 / Twitter

| | コメント (0)

2022/12/04

組合せテストにおける因子と水準はどちらを最優先で考えるべきか

ソフトウェアテストの組み合わせテストでは、因子と水準の考え方が重要になる。
組合せテストにおける因子と水準はどちらを最優先で考えるべきか、考えたことをメモ。

【参考】
システム開発で組合せテスト技法を使いこなすには? 4つのポイントを紹介| Qbook

組合せテストの考え方 - Qiita

駆け出しテストエンジニアと組み合わせテスト技法:Bugs Life ~テストエンジニアは不具合と戦い共に生きる:エンジニアライフ

ソフトウェアテスト技法練習帳はテストケースの切り方に困っている人向けにおすすめの本だ: プログラマの思索

実践した後に勉強するのがエンジニアの本来の道: プログラマの思索

プログラミングしてる時はでっかいピタゴラ装置を作ってるみたいな感じ: プログラマの思索

【1】水準はパラメータの値、因子はパラメータの値をグルーピングしたカテゴリ。
一般に、設計書からテストケースを作成する場合、因子と水準のどちらを最優先で考えるのか?

【2】基本的に考えると、因子を決めて、因子のインスタンスである水準を洗い出すように考えがち。
たとえば、都道府県という因子から、東京都、大阪府のような水準をトップダウンで導き出す。
つまり、因子→水準の順番を考えるように思ってしまう。

しかし、実際の仕様書は、魑魅魍魎な日本語という曖昧な自然言語で書かれている。
因子をきちんと書き出してくれていない場合が多い。
実は、仕様書やユーザーストーリーには、水準だけ記載されていて、因子が明示されていない場合が非常に多い。

たとえば、映画館の入場料金は、学生や老人は20%割引、深夜時間帯は30%割引、水曜はレディースデーで女性は割引、団体客は一括割引などの仕様が書かれていたとする。
すると、水準である実際のパラメータの値はたくさん書かれているが、因子は明示されていない。
因子は年齢、曜日、時間帯、性別などが出てくるが、たとえば、年齢と曜日の組み合わせならどの割引になるのか、が不明確なのだ。

つまり、実際の仕様書、ユーザマニュアル、ユーザストーリーには、水準はあちこちに散らばって記載されているが、因子は明確に記載されていない。
よって、組合せテストを作成する前には、まず仕様書から水準をすべて抽出し整理して、因子を改めて定義し、デシジョンテーブルに落とし込む必要がある。

すなわち、水準→因子の順番で考える場面のほうが非常に多い。

すると、因子の組合せパターンから、仕様書に記載されていないパターンが不明点としてたくさん出てくるだろう。
そんな内容をすべて特定し、ユーザに逐一確認して、仕様を決めていく必要が出てくる。
そういう作業こそが本来のSEの仕事なのだろう。

【3】映画館の入場料金の例のように、因子と水準を抽出してデシジョンテーブルを作成していく研修を実施した時がある。

すると、時間内にデシジョンテーブルを正確に作成できる人と、いくら時間があってもデシジョンテーブルを作成できない人の2つの集団に分かれるように認識した。

デシジョンテーブルを作成できない人は、因子→水準の順番で抽出しようとしているので、いつまで経っても、自分の頭が混乱した状態になっているようだ。
つまり、たくさんの水準のデータを抽出するが、因子と完全に対応して整理できないので、たくさんのデータに混乱してしまっているようだ。
一方、デシジョンテーブルを作成できる人は、まず水準を抽出して、因子でグルーピングする順番なので、水準→因子で作業しているから、漏れなく整理でき、組合せのパターンや関数従属性も考慮して、デシジョンテーブルを作成できているようだ。

どうやら、デシジョンテーブルを作成する能力は、ある人とない人では、作業工数が10倍以上も違うように思えた。
つまり、デシジョンテーブル作成の生産性は、人によって大きなバラツキがあるわけだ。

【4】僕は、水準から因子を定義し、デシジョンテーブルを決めていく作業は、データモデリングの作業にも近いように思う。
なぜならば、それらの因子はテーブルの1項目に普通は対応しているので、項目間に関数従属性が発生し、その制約によってデータモデルが出来上がっていくからだ。

換言すれば、水準から因子を抽出して定義する作業は、組合せテスト技法だけでなくデータモデリングにも通じる技術ではないか、と思う。

しかし、「水準→因子の順番で整理してデシジョンテーブルを作る」という手順さえ理解すれば、新人のプログラマであってもすぐに慣れていくだろうと考えている。
組合せテストのようにテストケース作成の技術は、教科書を習うよりもたくさんの経験をこなす方が重要だ。
まずはたくさん試して、失敗もして、慣れた後に教科書を見直す方が、経験が整理されるので良いと思う。

実践した後に勉強するのがエンジニアの本来の道: プログラマの思索

| | コメント (0)

« 2022年11月 | トップページ | 2023年1月 »