製品の品質を落とさずに開発チームを拡大する方法
開発チームの規模を拡大中ですか?製品の品質を犠牲にすることなく成長する方法を学びましょう。このガイドでは、スケールする時期、チーム構成、採用、リーダーシップ、ツールなどの兆候に加え、The Codestがどのように...
キャリアをスタートさせたばかりのプログラマーの多くは、変数、関数、ファイル、その他のコンポーネントの命名について、あまり重要でないと考えている。その結果、彼らのデザイン・ロジックはしばしば正しく、アルゴリズムは素早く実行され、望みの効果をもたらしますが、ほとんど読むことができません。この記事では、さまざまなコード要素に名前を付けるときに何を指針とすべきか、また、極端から極端にならないようにするにはどうすればよいかを簡単に説明しようと思います。
仮に、あなたとあなたの チーム を引き継いでいる。 コード 他のプログラマーから。その プロジェクト しかし、そのひとつひとつの要素は、もっとうまく書けたはずだ。
アーキテクチャに関して言えば、コード継承の場合、ほとんどの場合、それを手に入れたプログラマから憎悪と怒りを買うことになる。ある時は死につつある(あるいは絶滅した)技術の使用によるものであり、ある時は開発当初のアプリケーションに対する間違った考え方によるものであり、またある時は単に担当プログラマーの知識不足によるものである。
いずれにせよ、プロジェクトの時間が経つにつれて、プログラマーがアーキテクチャやテクノロジーに対して怒り狂うような状況になることはあり得る。結局のところ、どのアプリケーションも、時間が経てば、いくつかの部分の書き換えや、特定の部分の変更が必要になる。しかし、プログラマーを白髪にする問題は、受け継いだコードを読んで理解することの難しさである。
特に極端なケースとして、変数に意味のないアルファベット1文字が付けられていたり、関数に突発的な創造性が沸き起こり、アプリケーションの他の部分と整合性が取れていなかったりすると、プログラマーは暴走してしまうかもしれません。このような場合、正しい命名で迅速かつ効率的に実行できるコード解析は、例えば関数の結果を生成するアルゴリズムの追加解析を必要とします。そしてそのような解析は、目立たないとはいえ、膨大な時間を浪費します。
アプリケーションのさまざまな部分に沿って新しい機能を実装することは、それを分析するという悪夢を経験することを意味する。しばらくすると、その意図が明確でないため、コードに戻って再び分析しなければならない。
こうして、私たちはアプリケーションを支配する無秩序の竜巻に吸い込まれ、開発に関わるすべての人をゆっくりと消耗させていく。プログラマーはプロジェクトを嫌いになり、プロジェクトマネージャーは開発期間が絶えず伸び始める理由を説明するのを嫌いになり、顧客は信頼を失い、計画通りに何も進まないことに腹を立てる。
現実を直視しよう-スキップできないものもある。プロジェクトの初期に特定のテクノロジーを選択した場合、時間とともにサポートが終了したり、徐々に時代遅れになりつつある数年前のテクノロジーに精通するプログラマーが少なくなっていくことを意識しなければならない。ライブラリのアップデートによっては、多かれ少なかれコードの変更が必要になり、依存関係の渦に巻き込まれ、さらに身動きが取れなくなることがよくある。
もちろん、テクノロジーは古くなっていくが、それらを含むプロジェクトの開発時間を確実に遅らせる要因は、大部分が醜いコードである。もちろん、ここでロバート・C・マーティンの本について触れなければならない。これはプログラマーにとってのバイブルであり、著者は完璧を目指すコードを作るために従うべき優れたプラクティスと原則を数多く提示している。
この段落では、多かれ少なかれ良いものに変えることができる、いくつかの悪いコード・プラクティスを概説してみようと思う。この段落では、多かれ少なかれ良いものに変えることができる、いくつかの悪いコード・プラクティスを概説してみようと思う。
残念なことに、大学でさえもごく一般的に行われている恐ろしい習慣が、変数名を1文字で表すことだ。変数の目的を判断するための不必要な思考を避け、変数名に複数の文字を使う代わりに、例えばi、j、kのように1文字で済ませるのである。
逆説的だが、これらの変数の定義の中には、著者が何を考えていたかを決定する、もっと長いコメントが付けられているものもある。
ここでの良い例は、列と行の交点に対応する値を含む2次元配列に対する反復を表現することである。
const array = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
// かなり悪い
for (let i = 0; i < array[i]; i++) { { { for (let j = 0; j < array[i][j]; j++)
for (let j = 0; j < array[i][j]; j++) { // ここに内容がある。
// これがその内容だが、iとjが使われるたびに、それが何に使われているかを分析しなければならない。
}
// まだ悪いが、面白い
let i; // 行
let j; // 列
for (i = 0; i < array[i]; i++) { // 行を指定する。
for (j = 0; j < array[i][j]; j++) { // ここに内容がある。
// しかし、iとjが使われるたびに、それが何に使われているのかコメントをチェックしなければならない。
}
// はるかに良い
const rowCount = array.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { {.
const row = array[rowIndex];
const columnCount = row.length;
const column = row[columnIndex];
// 何が何だかわからない人はいないよね?
}
ある晴れた日、私はある人物が書いた高度に洗練されたコードに出くわした。 ソフトウェアエンジニア.このエンジニアは、ユーザーパーミッションを特定のアクションを指定する文字列として送信する場合、いくつかのビットレベルのトリックを使うことで大幅に最適化できることを突き止めた。
おそらく、ターゲットがコモドール64であれば、このような解決策でも問題ないだろうが、このコードの目的はJSで書かれたシンプルなウェブ・アプリケーションである。この癖を克服する時が来た:
ユーザーがコンテンツを変更するためのオプションは、システム全体で4つしかないとしよう。これらのパーミッションを、ステートを持つオブジェクトのキーか配列としてJSON形式で送信するのは、ごく自然なことだ。
しかし、私たちの賢いエンジニアは、4という数字が2進法におけるマジック・バリューであることに気づき、次のように計算した:

全能力表は16行あるが、これらのパーミッションを作成するアイデアを伝えるために4つだけリストアップした。パーミッションの読み方は以下の通り:
const user = { permissions:11 };
const canCreate = Boolean(user.permissions & 8); // true
const canRead = Boolean(user.permissions & 4); // false
const canUpdate = Boolean(user.permissions & 2); // true
const canDelete = Boolean(user.permissions & 1); // true
上に表示されているのは WebAssemblyコード.ここで誤解されたくないのは、このような最適化は、ある特定のことがごくわずかな時間やメモリ(あるいはその両方)で済む必要のあるシステムでは普通のことだということだ。一方、ウェブアプリケーションは、そのような過剰な最適化がまったく意味をなさない場所であることは間違いない。一般化したくはないが、フロントエンド開発者の仕事では、ビットの抽象化レベルに達するような複雑な操作はめったに行われない。
このようなコードを分析できるプログラマーは、この解決策にはどのような目に見えない利点があるのだろうか? 開発チーム より合理的な解決策に書き換えたい。
しかも、パーミッションを普通のオブジェクトとして送信すれば、プログラマーは1~2秒でインテントを読み取ることができるだろう。プロジェクトには何人かのプログラマーがいるはずで、それぞれがこのコードの断片に出くわさなければならない。その数バイトを保存する価値があるのだろうか?私の考えでは、ノーだ。
ウェブ開発 は急速に成長しており、この点に関してすぐに何かが変わるという兆候はない。最近、フロントエンド開発者の責任が大幅に増加したことは認めざるを得ない。彼らは、ユーザーインターフェイスにおけるデータのプレゼンテーションを担当するロジックの一部を引き継いだのだ。
このロジックが単純で、APIによって提供されるオブジェクトが単純で読みやすい構造を持っていることもある。しかし時には、ページ上のさまざまな場所に適応させるために、さまざまなタイプのマッピングやソートなどの操作が必要になる。そして、ここが沼にはまりやすい場所なのだ。
私は何度も、実行中の操作のデータを事実上読めなくしていることに気づいた。配列メソッドを正しく使い、適切な変数名をつけているにもかかわらず、ある時点での操作の連鎖は、私が達成したかったことの文脈を失いかけていた。また、これらの操作のいくつかは、時には別の場所で使う必要があり、時にはテストを書かなければならないほどグローバルだったり、高度だったりした。
const devices = [
{ id: '001', version: 1, owner: { location: 'Texas', age: 21 }.},
{ id: '002', version: 2, owner: { location: 'Texas', age: 27 }.},
{ id: '003', version: 3, owner: { location: 'Arizona', age: 27 }.},
{ id: '004', version: 2, owner: { location: 'Chicago', age: 24 }.},
{ id: '005', version: 2, owner: { location: 'Arizona', age: 19 }.},
{ id: '006', version: 3, owner: { location: 'Texas', age: 42 }.},
];
// かなり複雑
(() => {
const locationAgeMap = devices
.map(device => device.owner)
.reduce((マップ, アイテム) => { { もし!
if (!map[item.location]){。
map[item.location] = item.age;
} else {
map[item.location] += item.age;
}
return map;
}, {});
const locationLegalAgeOwnersMap = devices
.map(device => device.owner)
.filter(owner => owner.age >= 21)
.reduce((マップ, アイテム) => {
もし(!map[item.location]){なら
map[item.location] = [item];
} else {
map[item.location].push(item);
}
return map;
}, {});
console.log({ locationAgeMap, locationLegalAgeOwnersMap });
})();
// まだ複雑だが、よりコンパクトに
(() => {
const locationOwnersMap = devices
.map(device => device.owner)
.reduce((マップ, アイテム) => { { もし!
if (!map[item.location]){。
map[item.location] = [item];
} else {
map[item.location].push(item);
}
return map;
}, {});
const locationAgeMap = Object.fromEntries(
Object.entries(locationOwnersMap).map(([場所, 所有者]) => {)
const ownersAge = owners.map(owner => owner.age).reduce((a, b) => a + b);
return [location, ownersAge];
})
);
const locationLegalAgeOwnersMap = Object.fromEntries(
Object.entries(locationOwnersMap).map(([場所, 所有者]) => {)
const filteredOwners = owners.filter(owner => owner.age >= 21);
return [location, filteredOwners];
}).filter(([_location, owners]) => { オーナー.length > 0
return owners.length > 0;
})
);
console.log({ locationAgeMap, locationLegalAgeOwnersMap });
})();
これは私が伝えたいことを簡単に説明できるような些細なコードではない。そして、2つの例の計算の複雑さがわずかに異なることも知っている。アルゴリズムの違いは単純で、どちらも場所とデバイスの所有者のマップを用意する。
The first one prepares this map twice, while the second one prepares it only once. And the simplest example which shows us that the second algorithm is more portable lies in the fact that we need to change the logic of creating this map for the first one and e.g., make the exclusion of certain locations or other weird things called business logic. In case of the second algorithm, we modify only the way of getting the map, while all the rest of the data modifications occurring in the subsequent lines remain unchanged. In the case of the first algorithm, we need to tweak every attempt at preparing the map.
実際には、アプリケーション全体の特定のデータモデルを変換したり、リファクタリングしたりする必要がある場合、このようなケースはたくさんある。
様々なビジネスの変化に対応し続けることを避ける最善の方法は、かなり汎用的な方法で関心のある情報を抽出できるグローバルツールを準備することである。たとえ、最適化解除の代償として失うかもしれない2-3ミリ秒を犠牲にしても。
プログラマーは他の職業と同じように、毎日新しいことを学び、多くの間違いを犯します。最も重要なことは、これらの間違いから学び、自分の職業でより良くなり、将来そのような間違いを繰り返さないようにすることです。私たちの仕事が常に完璧であるという神話を信じることはできない。しかし、他の人の経験に基づき、欠点を減らすことはできる。
この記事を読むことで、少なくともいくつかのことを避けることができることを願っている。 悪しきコーディング慣行 私が仕事で経験したことベスト・コード・プラクティスに関するご質問は、下記までご連絡ください。 The Codestクルー 疑問点を相談するために。
続きを読む
ウェブアプリのセキュリティターゲット="_blank" 脆弱性
*タイトル画像はspeednet.plより