※本記事はアフィリエイトプログラムによる収益を得ています
ソフトウェア開発の品質・効率向上が求められている今、ソフトウェアレビュー(以下、レビュー)の重要性はますます高まっています。商用開発では「要件定義」「設計書」「ソースコード」「テスト計画」「運用手順書」などを対象としたレビューが行われていますし、オープンソースソフトウェアのプロジェクトでも、ソースコードリポジトリへのチェックインの前にソースコードレビューを推奨したり、義務付けたりしています。
しかし、レビューは自由度の高い活動です。レビュー会議では本質的な欠陥や問題を指摘しても、欠陥や問題に関係のない雑談をしていても、表面上は同じように実施したことになります。
皆さんも「レビューしていたはずなのに……」という状況を一度は経験したことがあるのではないでしょうか。次のような状況です。
―― サブシステムのリーダーが参加する進捗会議でシステムテストの結果を見ながら……
統括リーダー 「サブシステムXで見つかっている欠陥は、レビューで検出されていて良さそうな欠陥が多いように見える。原田さんのところだと思うが、レビューが不十分だったのでは?」
サブシステムXのリーダー原田氏 「詳細な業務知識が必要でロジックも複雑な部分だったので、この分野に詳しい片山さんにお越しいただいてレビューしていただいたのですが……」
統括リーダー 「うーん。でもこの程度の異常系の定義漏れがあるのはレビューの質を疑うなぁ。他にもスループットをだいぶ改善すべき部分があるのも気になる」
サブシステムXのリーダー原田 「……」
―― 帰宅途中の電車でレビューを振り返る原田
サブシステムXのリーダー原田 (そう言えば、片山さんはかなり忙しそうでレビュー会議で詳細設計書を初めて見る感じだった。目に付きやすい表記揺れの指摘をしていたような気がする。
片山さんとソリの合わない高原さんと些細な内容で長時間言い争っていて仲裁に苦労したんだっけか。そのせいか片山さん、高原さんの担当部分だと分かると必要以上に確認を繰り返していたような。そういうオレ自身も超多忙な片山さんのスケジュールが確保できて安心してしまって、レビュー会議中に重要な指摘をしてもらえているか、確認しなかったんだっけ。
「有識者が参加するレビューを実施した」というエビデンスができることで必要以上に安心してしまってた。久々に参加した千原さんと同期入社の鈴木さんも同じレビュー会議に参加していて、楽しそうに昔話をしていた。レビューの後半は時間不足になってドキュメントの終盤はざっと見るだけで終わってたな。あらためて考えると、見逃しがあっても仕方のないレビューだったのかもしれない。)
◇
「レビューの効果が出るかどうかは、レビューアーの知識やスキルに依存している」と考えられていることが多いにもかかわらず、有識者を呼んで準備万端なはずだったレビューの効果がそれほどでもなかったということも少なくありません。何が問題なのでしょうか?
連載の初回である今回は、レビューから効果を得るための前提を紹介します。前提の中には気を付けさえすればすぐにできるものもあれば、準備が必要であったり、定着まで少し時間がかかったりするものもあります。すぐに結果を求めず、少しずつ改善していきましょう。2回目以降で紹介するやり方や技法を取り入れる際には、今回の前提と併せて検討します。
本連載では、レビューを取りまとめる立場の方(リーダー)向けに、うまくいっていないレビューを改善していく方法を紹介していきます。拙著「間違いだらけの設計レビュー」では、リーダー、レビューアー、作成者(レビューイ)の3つの視点からレビューの具体的な手順を示し、レビューの方法とともに関係者が持つべきマインドを紹介しています。「レビューではどうやって欠陥や問題(※)を検出したらいいの?」という、基本的でありながら極めて重要な疑問に答えています。
※「欠陥」は「誤りや抜け」、「問題」は「欠陥にはなっていないものの欠陥の原因となる可能性が高く、修正しておいた方が良いもの」を指しています。
参考リンク:設計レビューに私情を持ち込んでいませんか?
本連載では、今のレビューを改善したいリーダーの視点から解決策を示します。もちろん、リーダー以外の立場でレビューに参加されている方にとっても、普段と異なる視点からレビューを知ることで、改善に役立つさまざまな気付きが得られるよう解説していきます。
では早速本論に入りましょう。今回は論点を8つのポイントに整理しています。まずはレビューの効果を得るための前提を示します。レビュー全体を通じたマインドの問題(1〜3)です。
Copyright © ITmedia, Inc. All Rights Reserved.
PREVIEW ';
}else{
mask.innerHTML = '
';
}
if((_preview && location.hash.indexOf('maskoff') !== -1) || (typeof itmIdLogin !== 'undefined' && itmIdLogin == 1)){
img.style.visibility = 'visible';
}else{
nxt.parentNode.insertBefore(mask,nxt);
}
}
}
};
/**
* mask_leadin : subscription マスクの実行
* @param {Object} マスク用パラメータ(sc,lc,ac,bc)と記事の状態(subscription_exist,subscription_objects,preview)を持った Object
*/
function mask_leadin(_p){
if(!_p.subscription_exist) return false;
addCSS('/css/spv/cmsInput.niche.css?date=202407081140');
let d = document;
let description = createElementWithAttribute('div',{"class":"colBoxDescription"},function(div){
div.appendChild(createElementWithAttribute('p','',function(p){
p.innerText = '続きを読むには、[続きを読む] ボタンを押して会員登録あるいはログインしてください。';
}));
});
let button = {};
if(_p.preview){
button.link = 'javascript:void(0)';
button.text = '続きを読む(TestMode)';
button.func = 'if(window.confirm(¥'マスクを解除しますか?¥')){location.href = ¥'#maskoff¥';location.reload();}else{return false;}';
} else {
button.link = 'https://id.itmedia.co.jp/isentry/contents?sc=' + _p.sc + '&lc=' + _p.lc + '&ac=' + _p.ac + '&cr=' + _p.cr + '&bc=' + _p.bc + '&return_url=' + encodeURIComponent(d.URL) + '&pnp=1&encoding=shiftjis';
button.text = '続きを読む';
button.func = 'cx_itm_mask_button();';
}
button.elem = createElementWithAttribute('div',{"class":"colBoxButton"},function(div){
div.appendChild(createElementWithAttribute('a',{"href":button.link,"onclick":button.func},function(a){
a.innerText = button.text;
}));
});
let mask = createElementWithAttribute('div',{"class":"mask_leadin"},function(div){
div.innerHTML = '
';
});
mask.querySelector('.colBoxIndex').appendChild(description);
mask.querySelector('.colBoxIndex').appendChild(button.elem);
let subscription = _p.subscription_objects[0];
subscription.classList.add( 'is-' + _p.subscription_type );
if((_p.preview && location.hash.indexOf('maskoff') !== -1) || (typeof itmIdLogin !== 'undefined' && itmIdLogin == 1)){
subscription.style.display = 'block';
}else{
subscription.parentNode.insertBefore(mask,subscription);
}
};
/*IMAGE MASK & Lead-in*/
(function(d){
let membersControl = searchMembersControl(); // %メンバー用マスクの存在チェックと対象要素取得
let imageMask = searchImageMask(); // 画像マスクの存在チェックと対象要素取得
let subscription = searchSubscription(); // subscription マスクの存在チェックと対象要素取得
let isPreview = d.domain.match(/(preview|broom|localhost)/);
let param ={
isLoginURL:'//status.itmedia.co.jp/isLoginAIT.cgi',
sc:'0c1c43111448b131d65b3b380041de26f2edd6264ee1c371184f54d26ab53365',
lc:'7d7179c146d0d6af4ebd304ab799a718fe949a8dcd660cd6d12fb97915f9ab0a',
ac:'1a599d548ac1cb9a50f16ce3ba121520c8ab7e05d54e097bfa5b82cb5a328a0f',
bc:1,
members_control_exist:membersControl.exist,
image_mask_exist:imageMask.exist,
image_mask_objects:imageMask.Objects,
subscription_exist:subscription.exist,
subscription_objects:subscription.Objects,
subscription_type:subscription.type,
preview:isPreview
};
let setExtraMask = function(){
if (d.getElementById('isLogin') || isPreview) {
// isLogin 呼び出し済みであれば各マスク処理を実行
if (imageMask.exist) {
param.cr = '2c93f81754142e105c8bca17824745d14c8c4d69e9d7ede513e5530546e97641';
mask_images(param);
}
if (subscription.exist) {
param.cr = '90cfa6d666682f8b5dc3c798020e432fc294ef430deb069008d4f8bceeb02418';
mask_leadin(param);
}
} else {
// isLogin がなければ、呼び出した後で再実行
let js = mask_images.setISLOGIN(param.isLoginURL,param.sc);
js.addEventListener('load',function(){
setExtraMask();
});
}
};
if( subscription.type != 'force' ) { //強制 subscription ではない場合のみ実行
if (imageMask.exist || subscription.exist) {
setExtraMask();
}
}
})(document);
';
w.removeEventListener('scroll',arguments.callee,false);
htmlRequest(_xhrfile,_idname);
elem.setAttribute('data-status','true');
console.log('finished : ' + _idname);
}else{
// console.log('retry : ' + _idname);
}
}else{
e_loader.innerHTML = '
';
w.removeEventListener('scroll',arguments.callee,false);
htmlRequest(_xhrfile,_idname);
elem.setAttribute('data-status','true');
console.log('finished : ' + _idname);
}
}
};
w.addEventListener('scroll',scrolling,false); // スクロールイベント
scrolling(); // スクロールイベント(閲覧位置が半端な場合のために 1 回実行させる)
};
w.addEventListener('load',loading,false); // LOAD 後に実装
};