ソフトウェア設計論

まつ本

何の授業をしようか・・・

SW設計の内容であること

単なる前提だが広い
前半:上流(要求・見積・RFP),楠本先生
後半:下流?

役立つ内容にしたい

情報科学研究科の最大公約数を
幸いSW設計や下流は汎用的で役に立ちやすい
理論よりも実践

知らなさそうな話をしたい

B3授業 (演習D・実験B) の経験から考える

モダンSW開発

モダンSW開発

モダンSW開発 -実装-


目次

・良い名前をつける
・コメントはない方が良い

・動くの先にある良いプログラム
・良いプログラムとは?

・演習

"Don't call us, we'll call you"
・goto不要論からの学び
・できないことを増やす

・Complex vs Complicated
・分割統治
・DRY・KISS・YAGNI

良い名前をつける

良いプログラミングの第一歩
簡単で効果あり

- return result; // 抽象的すぎて何も伝わらない
+ return count;  // 具体的な名前を
- int delay = 1000;   // delay WHAT?
+ int delayMs = 1000; // 単位をつけるべき
if (err) {
  String s = ..; // スコープが小さいならサボってもOK
  print(s);
}
- getData();     // 抽象的すぎて何も伝わらない
+ getUserId();   // 具体的な名前を
- getAverage();       // 開発者の期待を裏切る名前
+ calculateAverage(); // 計算コストを要する旨を伝える

プログラムの名前の研究

雑談

良い名前をつけるには?

色のある動詞を考える

get よりも compute calculate retrieve extract

分離する (関数名の場合)

名前をつけられない ≒ やりすぎ

他者のソースコードを読む git/builtin/clone.c

int cmd_clone(int argc, const char **argv, const char *prefix)
  if (argc == 0)
    usage_msg_opt(_("You must specify a repository to clone."),
      builtin_clone_usage, builtin_clone_options);
$ git clone
fatal: You must specify a repository to clone.
usage: git clone [<options>] [--] <repo> [<dir>]

レポートより





コメントを書くのを忘れていた
今後はできる限りコメントを書きたい

コメントはない方が良い

コードから伝わることを書かない

class User {
  // constructor
  public User() {
    // initialize
    String name = "";
    ..
  }
  // find users by query
  List<User> find(Query q) { ..

役に立たない
ノイズにしかならない
ファイルがでかくなる
メンテが大変

コードとコメントの不一致

雑談

F. Wen et al., Int'l Conf. Program Comprehension (ICPC) 2019.

コメントの作法 (1/2)

コメントがなくても伝わるように

- // ボタンを押した瞬間
- if (buttonState == HIGH && lastButtonState == LOW) {
+ if (hasButtonPressed()) {
void doSomething() {
- // setup
- ..
- // calculate
- ..
- // show
- ..
+ setup();
+ calculate();
+ show();

関数化は分割統治手法でもあり命名手法でもある

コメントの作法 (2/2)

コードから分からないことをコメントに書く

TODOコメント

// TODO: null時の処理を追加する
// FIXME: 処理が重いので最適化すべき

何もしないことの明記

while (offset < size && buf[offset++] != '\n') {
  ; // do nothing
}

複雑な命令の補足

// email matcher (e.g., test@example.com)
Pattern.compile("^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$");

一時策であることの明記

// workaround for issue#30

モダンSW開発 -実装-


目次

・良い名前をつける
・コメントはない方が良い

・動くの先にある良いプログラム
・良いプログラムとは?

・演習

"Don't call us, we'll call you"
・goto不要論からの学び
・できないことを増やす

・Complex vs Complicated
・分割統治
・DRY・KISS・YAGNI

動くプログラム

プログラミング言語の基本命令

変数の宣言 int i String s var v
四則演算 sum+100 i++ total*rate
条件分岐 if(..) switch(..)
繰り返し for(..) while(..) (=特殊な条件分岐)

関数呼び出し print("err") sort(arr) str.lower()

これだけであらゆる処理が可能

チューリング完全
意図通りに動くプログラムは作れる

ここで満足するのはNG
動くようになったら「良い」を考えるべき

プログラミングの習得レベル

Lv1. アルゴリズムとデータ構造

Lv2. 構文・文法

Lv1+Lv2で動くプログラムは作れる

Lv3. パラダイム

構造化・オブジェクト指向・関数型・宣言型

パラダイムの理解には様々な概念の理解が必要

  • OOP:カプセル化・継承・移譲
  • 関数型:参照透過性・冪等性・純粋性・副作用

Lv2は具体的で簡単, Lv3は抽象的で難しい

動くの先にある良いプログラム

Lv3パラダイムの理解が重要

Lv2で満足してはいけない
Lv3を習得すると劇的にプログラミングが上達する

LibやFWを使う際に開発者の気持ちがわかる
暗黙の了解がいたるところにある

理論を理解すれば実践が楽になるのと同じ
理論と実践の両方に取り組むべき

ただしLv2の段階でも様々な良さがある

適切な名前・関数分割・浅いネスト等

Lv3は体系的な知識が必要

実践だけでは身につかない

コードだけ眺めていても分からない
Web検索による断片的知識でも怪しい
手を動かさない時間が必要

歴史の勉強も有用

非構造 → 構造 → OOP・関数型・宣言的
どういう背景・問題からその概念が生まれたのか?

複数のプログラミング言語に触るべき

比較するとわかることも多々ある
例)海外に行くと日本の車道の平らさがわかる

良いプログラムとは?

信頼性・効率性 (実行的側面の良さ)

目的を満たすか?バグがないか?
計算リソースの無駄がないか?

可読性・保守性

読みやすいか?意図を汲み取れるか?

拡張性

拡張時の作業は書き換えか?追加か?

テスタビリティ

main() vs main()+sub1()+sub2()+sub3()

モダンSW開発 -実装-


目次

・良い名前をつける
・コメントはない方が良い

・動くの先にある良いプログラム
・良いプログラムとは?

・演習

"Don't call us, we'll call you"
・goto不要論からの学び
・できないことを増やす

・Complex vs Complicated
・分割統治
・DRY・KISS・YAGNI

演習 (10m)

リファクタリング前後のソースを比較せよ

実際のリファクタリング事例3つに対して
以下2点を答えよ

  • どういうリファクタリングなのか?
  • 何が改善されているのか?

提出方法

以下書式のテキストをCLEに提出すること

# case 1
## リファクタリングの内容
...

## 改善
...

case 1

- if (do_set_head && set_head(remote_refs, transport->remote))
-     ;
+ if (do_set_head) {
+     set_head(remote_refs, transport->remote);
+ }

case 2

+   int ret;
    if (url)
-       return get_urlmatch(argv[0], url);
-   return get_value(argv[0], value_pattern, flags);
+       ret = get_urlmatch(argv[0], url);
+   else
+       ret = get_value(argv[0], value_pattern, flags);
+
+   return ret;
  }

case 3

-   rerere_cmd.git_cmd = 1;
-   strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL);
-   if (run_command(&rerere_cmd))
-       die(FAILED_RUN, rerere_cmd.args.v[0]);
+   if (maintenance_task_rerere_gc(&opts, &cfg))
+       die(FAILED_RUN, "rerere");
+static int maintenance_task_rerere_gc(/* 略 */)
+{
+   rerere_cmd.git_cmd = 1;
+   strvec_pushl(&rerere_cmd.args, "rerere", "gc", NULL);
+   return run_command(&rerere_cmd);
+}

3つはgit/gitの事例

case 1

That's not wrong, but it really hides the point of the line, which
is (maybe) calling the function.

case 2

Refactor functions to have a single exit path. This will make it
easier in subsequent commits to add common cleanup code.

case 3

In a subsequent commit we are going to introduce a new "rerere-gc"
task for git-maintenance(1). To prepare for this, refactor the
code that spawns `git rerere gc` into a separate function

モダンSW開発 -実装-


目次

・良い名前をつける
・コメントはない方が良い

・動くの先にある良いプログラム
・良いプログラムとは?

・演習

"Don't call us, we'll call you"
・goto不要論からの学び
・できないことを増やす

・Complex vs Complicated
・分割統治
・DRY・KISS・YAGNI

"Don't call us, we'll call you"

制御の反転・ハリウッド原則

制御の主となるmain()を自分で書かない
フレームワークはこの考えに基づく

普通の制御

[My source] // 我々のプログラムがLibを呼び出す
  ↓ call
[Library]

制御の反転

[Framework] // main関数はここ
  ↓ call
[My source] // 我々のプログラムはFWから呼ばれる
  ↓ call
[Library]

制御の反転の一例

Flask (Pythonの軽量WebアプリFW)

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello_world():
  return "<p>Hello, World!</p>"

http://localhost:5000/<p>Hello, World</p>

Arduino言語

void setup() {
  // 最初に一度だけ呼ばれる
}
void loop() {
  // 無限に何度も呼ばれる
}

制御の反転のPros・Cons

Pros

FWを利用する開発者はとても楽
共通の処理をFWに任せる
システム全体の制御を考えなくて良い
関心の分離が可能

Cons

できることに制限が生まれる
かゆいところに手が届かない

FWに対する深い理解が必要

  • いつcallback関数が呼ばれるか?
  • 開発者の気持ちを理解するべき

goto有害論

E.W. Dijkstra, Communications of the ACM, 1968

X Considered Harmful

雑談

gotoの例

qsort:
    PUSH    {R0-R10,LR}
    MOV     R4,R0
    MOV     R5,R1
    CMP     R5,#1
    BLE     qsort_done         # goto文
    CMP     R5,#2
    BEQ     qsort_check        # goto文
    ..
qsort_check:
    LDR     R0,[R4]
    LDR     R1,[R4,#4]
    CMP     R0,R1
    BLE     qsort_done         # goto文
    ..
qsort_done:
    POP     {R0-R10,PC}

ARMアセンブリ言語によるQSort

goto文の何が悪いのか?

goto文は一見すごい命令

高い汎用性:分岐・繰返・関数の基本要素
使いやすい:比較 CMP + ジャンプ JMP

プログラムの制御を自在に変更できる

何でもできるのが良くない

無秩序の種
いわゆるスパゲッティ🍝コードの根源

天才なら制御できるかもしれないが常人には無理
天才は1年後自分のコードを理解できるか?
天才でも常人への説明責任がある

gotoを許すフローチャート

D.E. Knuth, RUNCIBLE -algebraic translation on a limited computer, Commn ACM, 1959

goto不要論からの学び

汎用的・使いやすいは必ずしも正義ではない

目的に特化している方が意図が明確

CMP JMP ではなく if()while()
 → 構造化プログラミング

var ではなく intstring,型なしよりも型あり

自前main()より制御の反転

できることを制限したほうが良い場合がある

意図が明確になる
物事が単純になる
例外がなくなる
秩序が生まれる

文章でも同じことが言える

雑談

具体的な語を選ぼう

SZZは~するものである

SZZは~する手法である

「もの」「こと」は汎用的で使いやすいが曖昧


~における~

~に関する~

「~のようなかたちです」

「そんな感じです」

レポートより





グローバル変数はとても使いやすかったので
今後積極的に使っていきたい

できないことを増やす

globalよりもlocal, publicよりもprivate

変数・フィールドの可視性 (見える範囲) を下げる
変数・フィールドが及ぼしうる影響範囲を最小限に

可変よりも不変

変数の書き換えを禁止する

Rustはデフォルトで不変

let x = 5;     // 不変(デフォルト)
let mut y = 5; // 可変

自前main()よりも制御の反転

自分でやらずに誰かに任す
バグを減らす一つの方法はコードを書かないこと

モダンSW開発 -実装-


目次

・良い名前をつける
・コメントはない方が良い

・動くの先にある良いプログラム
・良いプログラムとは?

・演習

"Don't call us, we'll call you"
・goto不要論からの学び
・できないことを増やす

・Complex vs Complicated
・分割統治
・DRY・KISS・YAGNI

Complex vs Complicated


https://www.gilkisongroup.com/investing-complicated-or-complex/

Complex vs Complicated

https://larrycuban.wordpress.com/2023/08/15/important-differences-between-complicated-and-complex-systems-rockets-to-the-moon-and-public-school-classrooms/

分割統治

大きな問題を制御可能な程度に小さく分解する

大きな問題に体当たりしてはいけない
小さく分解して一つずつ解決する
小さくテストできる
優先順位をつけるとベター

エンジニアリングの基本

ComplexをComplicatedにする第一歩

プログラミング以外にも適用できる

  • パーティーの準備をする
  • 授業の準備をする
  • 研究する

実験スクリプトの例

題材

1. 指定ディレクトリ内のファイルを探索する
2. 発見ファイルに外部ツールXを適用する (前処理)
3. ツールXの出力結果に処理Yを適用する (本処理)
4. その結果を指定ディレクトリに書き出す

いきなりこれを実装しない

$ analyze.py in-dir/ out-dir/

分解して取り組む

$ apply-x.py in-file out-file
$ apply-y.py in-file out-file  # 最優先で取り組むべき
$ find in-dir -type f | xargs -i apply-x.py {} out-dir/{}

DRY - 繰り返すな

Don't repeat yourself

繰り返すとメンテが大変
変更の際に多くの書き換えが発生する
テストもしにくくなる

...
print("[ERROR] Something happens")
...
print("[WARN] Something happens")
...
log(log.ERROR, "Something happens")
...
log(log.WARN, "Something happens")

KISS - 単純にしておけ

Keep it simple stupid

複雑なほどバグが増, 可読性も低, メンテも大変
最小限にしておく

int times(n, m) {
  int sum = 0;
  for (int i = 0; i < n; i++) {
    sum += m;
  }
  return sum;
}
n * m;

関数を切り出すときもKISSを考える
処理内容を一言で説明できる, かつ単純にする

YAGNI - それたぶんいらんよ

You aren't gonna need it

未来のための拡張の余地を作らない
必要になったときに実装する

OOPでよく陥る
今必要でないならやらない

interface X {
  ...
}
public class Y implements X {
  ...
}

怠惰の理由にしないように

モダンSW開発 -実装-


目次

・良い名前をつける
・コメントはない方が良い

・動くの先にある良いプログラム
・良いプログラムとは?

・演習

"Don't call us, we'll call you"
・goto不要論からの学び
・できないことを増やす

・Complex vs Complicated
・分割統治
・DRY・KISS・YAGNI

良いプログラマ

プログラミングは機械との対話でだけはない

人との対話という側面もある
「人」には他者だけでなく未来の自分を含む

人に伝わるコードを書こう
それが結果的に自分を助ける

良いプログラマはコミュニケーションもうまい

数学的素養だけでなく国語的素養
論理的思考力だけでなく言語能力
(叙情的・詩的な力は不要)

客観視能力も必要
相手がどう感じるかを類推する

--- # ソフトウェア工学基礎知識体系 ## SWEBOK Software Engineering Body of Knowledge IEEEが作っているSEの知識体系 知識や概念の「体系化・構造化」が目的 例:`節足動物門 > 昆虫綱 > コウチュウ目 > カブトムシ亜目 ...` 内容は薄くて広い 構造付きの辞書とみなすと良い ## 他にもいろいろなBOKがある Project Management BOK Data Management BOK 主にIT分野 --- # SWEBOK 目次 ## 全15章 ``` 1. SW要求 2. SW設計 3. SW構築 4. SWテスティング 5. SW保守 6. SW構成管理 7. SWエンジニアリング・マネージメント 8. SWエンジニアリングプロセス 9. SWエンジニアリングモデルおよび方法 10. SW品質 11. SWエンジニアリング専門技術者実践規律 12. SWエンジニアリング経済学 13. 計算基礎 14. 数学基礎 15. エンジニアリング基礎 ``` --- # SWEBOK 目次 ## SW設計の部分を抜粋 ``` 2. SW設計 - SW設計の基礎 - SW設計における主要な問題 - 並行処理 - イベントに対する制御と処理 - データの永続化 - コンポーネントの分散化 - エラー・例外処理 - 対話と表示 - セキュリティ - SW構造とアーキテクチャ - UI設計 - SW設計品質の分析と評価 - SW設計のための表記 - ... ``` --- # SWEBOK 目次 ## 全15章 ``` 1. SW要求 2. SW設計 3. SW構築 // 今日はここ ★★★★ 4. SWテスティング // 次回 5. SW保守 6. SW構成管理 7. SWエンジニアリング・マネージメント 8. SWエンジニアリングプロセス 9. SWエンジニアリングモデルおよび方法 10. SW品質 11. SWエンジニアリング専門技術者実践規律 12. SWエンジニアリング経済学 13. 計算基礎 14. 数学基礎 15. エンジニアリング基礎 ```

--- # FWの利用方法 FWのルールに則ってプログラムを作る どんな時に・何をしたいかだけを記述する (!!) あとはFWに任す 雑多な共通処理は全部FWがやってくれる システム全体の制御を考えなくてよい

https://vaelen.org/post/arm-assembly-sorting/

--- !-- _class: enshu-- # 演習 <sub>(10m)</sub> ## 自身のプログラムをコードレビューせよ 題材はなんでもOK - 学部の演習・実験 - 研究用の実験スクリプト 1.可読性・保守性の問題点を3つ以上列挙せよ - リソース効率や機能不備は対象外 - 名前等の字面だけでなく構造的な問題も 2.問題点それぞれについて具体的な改善を考えよ ## 提出方法 1と2をまとめたテキストをCLEに提出すること