OSS-DB試験勉強 - 黒本ch2 インストールと設定
PostgreSQL公式チュートリアル: Installation from Source Code
https://www.postgresql.org/docs/12/installation.html
環境を汚したくないのでコンテナで遊ぶ
ビルド環境絡み
build-essential
- 入れないと
configure: error: no acceptable C compiler found in $PATH
と怒られる
- 入れないと
libreadline6-dev
psql
でコマンドライン編集・ヒストリ機能を利用するのに必要- configureの
--without-readline
オプションで無効化可能
zlib1g-dev
pg_dump
,pg_restore
機能に必要- configureの
--without-zlib
オプションで無効化可能
configure のオプション
問題集で触れられていたもの
--prefix=DIRECTORY
- インストール場所指定
--bindir
等に使われる
--with-openssl
- 暗号化コネクション機能有効化
crypto
ライブラリ必須apt-get install libssl-dev
--enable-debug
--with-pgport=NUMBER
- サーバ/クライアントのデフォルトポート番号指定
- 未指定の場合5432
--with-krb5
- Kerberos5認証を行う場合に使用する
- PostgreSQL version 9.5で消えたみたい
- Kerberos5認証を行う場合に使用する
--with-perl
--with-python
変遷まとめ記事。ありがたい
https://qiita.com/nuko_yokohama/items/e2d479a1af0de6cc50a6
gmakeコマンドに関して
https://github.com/postgres/postgres/blob/master/GNUmakefile.in
make
ormake all
- ビルド
- べつにrootである必要なし
- ビルド
make check
- 正常動作検証
- 通らないとこういうエラーが出る:
pg_regress: initdb failed Examine /work/postgresql-12.1/src/test/regress/log/initdb.log for the reason. Command was: "initdb" -D "/work/postgresql-12.1/src/test/regress/./tmp_check/data" --no-clean --no-sync > "/work/postgresql-12.1/src/test/regress/log/initdb.log" 2>&1 make[1]: *** [check] Error 2
make install
- インストール(コピー)
--prefix
で指定の場所- デフォルトで
/usr/local/pgsql/bin
- インストール(コピー)
make distclean
- configureコマンドで間違ったオプションを指定してしまった場合に実行することが推奨されている
環境変数
https://www.postgresql.org/docs/current/libpq-envars.html
環境変数 | 対応するパラメータ |
---|---|
PGHOST | host |
PGHOSTADDR | hostaddr |
PGPORT | port |
PGDATABASE | dbname |
initdb
https://www.postgresql.org/docs/12/app-initdb.html
/usr/local/pgsql/bin/initdb
というexecutableがあるpg_ctl initdb
でも呼び出せる
- 新しいPostgreSQLデータベースクラスタを作成する
PostgreSQLデータベースクラスタとは
- 単一のサーバーにより管理されるデータベース群
initdbを実行すると、デフォルトで3つのデータベースができる
/# su postgres /$ initdb /$ psql
postgres=# \? ... \l[+] [PATTERN] list databases ... postgres=# \l \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -----------+----------+----------+------------+------------+----------------------- postgres | postgres | UTF8 | en_US.utf8 | en_US.utf8 | template0 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | en_US.utf8 | en_US.utf8 | =c/postgres + | | | | | postgres=CTc/postgres (3 rows)
- template1は、データベース作成時にデフォルトで使用される雛形
- 必ず作成するテーブル等を定義しておくとよい
- PostgreSQL 9.0からはデフォルトでPL/pgSQL言語インストール
- cf. 昔は
createlang
コマンドが必要だった
- cf. 昔は
/$ createlang
Error: pg_wrapper: createlang was not found in /usr/lib/postgresql/12/bin
contribディレクトリ
https://www.postgresql.org/docs/12/contrib.html
- PostgreSQL本体に取り込まれていないモジュール
- 限られたユーザ向け
- 実験的すぎ
- contribution の略
- ユーザから寄贈された、の意
- 含めたい場合は、ソースコードからインストールする際
make all
の代わりに
make world
を使用する
postgresql.conf
#
始まりの行はコメント- サーバ再起動が必要なものの反映方法
pg_ctl stop
+pg_ctl start
- または
pg_ctl restart
- 再起動の必要ないものの反映方法
- SIGHUPを送る(1番)
- または
pg_ctl reload
接続・認証
https://www.postgresql.org/docs/12/runtime-config-connection.html
軒並みサーバ再起動が必要
黒本で触れられているもの
listen_addresses
(string)port
(integer)- TCPポート番号(5432)
max_connections
(integer)- 最大接続数(100)
- IPCパラメータの設定が必要なことも
superuser_reserved_connections
(integer)- スーパーユーザのために予約する接続数(3)
max_connections
が100、本パラメータが3なら、一般ユーザは97接続まで
- スーパーユーザのために予約する接続数(3)
リソース、WAL: Write Ahead Log
https://www.postgresql.org/docs/12/runtime-config-resource.html
https://www.postgresql.org/docs/12/runtime-config-wal.html
shared_buffers
(integer in MB)- デフォルト値は小さめ
- PostgreSQL 9.0だと
- 【補】PostgreSQL 12だと128MB
- スペックが低いH/Wでも動作するための配慮
- 実運用を考慮してサイズを決めよう
- 設定反映にサーバ再起動必要
- デフォルト値は小さめ
temp_buffers
(integer in MB)- データベースセッションが一時的に使用するメモリ
- ソートなど
- データベースセッションが一時的に使用するメモリ
maintenance_work_mem
(integer in MB)- 保守作業用メモリ
- VACUUM
- CREATE INDEX
- ALTER TABLE ADD FOREIGN KEY
- 等々
- 保守作業用メモリ
wal_buffers
(integer in MB)- ロク先行書き込み(トランザクションログ)用に共有メモリで確保される確保されるメモリサイズ
- 設定反映にサーバ再起動必要
Clean Code ch12 Emergence
- Getting Clean via Emergent Design
- Simple Design Rule 1: Runs All the Tests
- Simple Design Rules 2-4: Refactoring
- No Duplication
- Expressive
- Minimal Classes and Methods
- 英語
Getting Clean via Emergent Design
- Kent Beckによる4つのルール
- Runs all the tests
- Contains no duplication
- Expresses the intent of the programmer
- Minimizes the number of classes and methods
- これに従えば、そのシステムは「シンプル」
- 重要度順
Simple Design Rule 1: Runs All the Tests
- システムが理屈上完璧な設計でも、実際に意図通りに動作することを簡単に検証できないならば、疑わしい
- テストできないシステムは動作を検証できない
- 動作を検証できないシステムはデプロイされるべきではない
- 幸い、テスト可能にすると良い設計へ向かう
- まずい設計だとテストを書くのが困難
- 逆に、テストを書けば書くほど、テストを書きやすい良い設計になる
- Single Responsibility
- 疎結合・高凝集
Simple Design Rules 2-4: Refactoring
- テストがあれば変更が怖くない
- 変更が怖くないので種々のリファクタリングを行える
- 先述のルールのうち3つはこれで適用できる
No Duplication
- 低水準の小さな部分の重複: private関数を抽出する
- SRP違反も疑い、別クラスに切り出してpublicにすることも検討する
- 【補】単体テスト可能になる
- SRP違反も疑い、別クラスに切り出してpublicにすることも検討する
- 高水準のアルゴリズムの重複: Template Method Patternを適用する
Expressive
- 長い長い運用のコストを下げるために
- 良い命名
- 関数やクラスを小さくする
- 命名しやすい
- 書きやすい
- 理解しやすい
- 標準の命名法を使用する
- よく知られたデザインパターンから名前をとるなど
- よくできた単体テスト
- 単体テストの主目的は例示によるドキュメンテーション
- 一番重要なのは、気遣い
- 【補】try の意訳
- 次にそのコードを読む人が読みやすくなるように
- たいてい自分自身ですよ
Minimal Classes and Methods
- クラスやメソッドを小さくしようとしすぎると、数が増えすぎる問題
- システムのサイズも小さくするために、クラスや関数の数は最小限に
- ただし先述の3ルールよりは優先度低い
- 【補】A Philosophy of Software Design でもこのことには触れられている
- モジュールのインタフェースの数が増えるということは、学習コスト = 認知の負荷が増え、複雑性が増すということ
英語
- nomenclature
- 命名法
OSS-DB試験対策 緑本 ch9 組み込み関数と演算子
集約関数
しってるから略
count()
sum()とavg()
min()とmax()
比較演算子
しってるから略
算術関数と演算子
算術関数
いろいろある
div()とmod()
postgres=# SELECT div(13, 3), mod(13, 3); div | mod -----+----- 4 | 1 (1 row)
- 商と剰余
- 【補】マイナス周りの挙動
postgres=# SELECT div(-13, 3), mod(-13, 3); div | mod -----+----- -4 | -1 (1 row) postgres=# SELECT div(13, -3), mod(13, -3); div | mod -----+----- -4 | 1 (1 row) postgres=# SELECT div(-13, -3), mod(-13, -3); div | mod -----+----- 4 | -1 (1 row)
- せいしつ
- 剰余はマイナスの値をとる
- (商 x 除数 + 剰余)は被除数に戻る
- 商はプラス割るプラスの符号違い
/
演算子との違い
postgres=# SELECT 13/3, -13/3, 13/-3, -13/-3; ?column? | ?column? | ?column? | ?column? ----------+----------+----------+---------- 4 | -4 | -4 | 4 (1 row) postgres=# SELECT 13/3, 13.0/3, 13/3.0; postgres-# ; ; ?column? | ?column? | ?column? ----------+--------------------+-------------------- 4 | 4.3333333333333333 | 4.3333333333333333 (1 row)
floor()とceil()
postgres=# SELECT floor(4.2), ceil(4.2); floor | ceil -------+------ 4 | 5 (1 row) postgres=# SELECT floor(-4.2), ceil(-4.2); floor | ceil -------+------ -5 | -4 (1 row)
round()とtrunc()
- round, truncともに0が基準
- round: 四捨五入 (0側を捨てる)
- trunc: 0側に切り捨て
postgres=# SELECT digit, round(14.05, digit), trunc(14.05, digit) FROM (SELECT unnest as digit from unnest(ARRAY[-2,-1,0,1,2])) as digits; digit | round | trunc -------+-------+------- -2 | 0 | 0 -1 | 10 | 10 0 | 14 | 14 1 | 14.1 | 14.0 2 | 14.05 | 14.05 (5 rows)
random()
- [0;1)
算術演算子
- おもしろいやつだけ
postgres=# SELECT |/2 as sqrt, ||/2 as cbrt; sqrt | cbrt --------------------+-------------------- 1.4142135623730951 | 1.2599210498948734 (1 row)
- 累乗
- 0の0乗は1みたい
postgres=# SELECT 2^8, 0^0; SELECT 2^8, 0^0; ?column? | ?column? ----------+---------- 256 | 1 (1 row)
- 階乗
postgres=# SELECT 4!, !!4; ?column? | ?column? ----------+---------- 24 | 24 (1 row)
- 絶対値
- スペース必要
postgres=# SELECT @ -1; ?column? ---------- 1 (1 row)
文字列演算子と述語
「||」演算子
postgres=# SELECT 'Kirima' || ' ' || 'Syaro'; ?column? -------------- Kirima Syaro (1 row)
LIKE
しってる
SIMILAR TO
- LIKE + 拡張正規表現も使える
- 暗黙的に
/^
と$/
で囲まれる感じ
「~」演算子
- 部分一致検索用
一致で真 | 不一致で真 | |
---|---|---|
case sensitive | ~ | !~ |
ignore case | ~* | ~* |
postgres=# SELECT 'banana' ~* 'BAN'; ?column? ---------- t (1 row)
文字列関数
lower()とupper()
postgres=# SELECT lower('String'), upper('String'); lower | upper --------+-------- string | STRING (1 row)
substring()
postgres=# SELECT substring('relationship' from 1 for 8); substring ----------- relation (1 row)
- 1文字目が
from 1
であることに留意する- 0にするとlengthから1つ減った感じになる
- negative lengthは不可
replace()
postgres=# SELECT replace('JavaScript', 'java', 'ecma'); replace ------------ JavaScript (1 row) postgres=# SELECT replace('JavaScript', 'Java', 'ECMA'); replace ------------ ECMAScript (1 row)
- 【補】ignore caseとかしたい場合は
regexp_replace
関数をつかう
postgres=# SELECT regexp_replace('JavaSciprt JavaScript', 'java', 'ECMA'); regexp_replace ----------------------- JavaSciprt JavaScript (1 row) postgres=# SELECT regexp_replace('JavaSciprt JavaScript', 'java', 'ECMA', 'i'); regexp_replace ----------------------- ECMASciprt JavaScript (1 row) postgres=# SELECT regexp_replace('JavaSciprt JavaScript', 'java', 'ECMA', 'ig'); regexp_replace ----------------------- ECMASciprt ECMAScript (1 row)
octet_length() / char_length()
postgres=# SELECT octet_length('あいうえお'),char_length('あいうえお'),char_length('あいうえお'); octet_length | char_length | char_length --------------+-------------+------------- 15 | 5 | 5 (1 row)
trim() / lpad() / rpad()
- 文字列の端を落とす
- 文字を指定しない場合、デフォルトで空白文字
postgres=# SELECT trim(leading '*' from '****hoge****'); ltrim ---------- hoge**** (1 row) postgres=# SELECT trim(trailing '*' from '****hoge****'); rtrim ---------- ****hoge (1 row) postgres=# SELECT trim(both '*' from '****hoge****'); btrim ------- hoge (1 row) postgres=# SELECT trim('*' from '****hoge****'); btrim ------- hoge (1 row)
- ltrim,rtrim,btrimでも同様のことができる
from
とかは渡さない
postgres=# SELECT ltrim('****hoge****', '*'); ltrim ---------- hoge**** (1 row) postgres=# SELECT rtrim('****hoge****', '*'); rtrim ---------- ****hoge (1 row) postgres=# SELECT btrim('****hoge****', '*'); btrim ------- hoge (1 row)
- lpad/rpad: パディング追加
- 文字を指定しない場合、デフォルトで空白文字
postgres=# SELECT lpad ('piyo', 10, '*'); lpad ------------ ******piyo (1 row) postgres=# SELECT rpad ('piyo', 10, '*'); rpad ------------ piyo****** (1 row)
日付 / 時刻の関数と演算子
現在の日付 / 時刻を取得する関数
postgres=# BEGIN; postgres=# SELECT current_timestamp,statement_timestamp(),clock_timestamp(); current_timestamp | statement_timestamp | clock_timestamp -------------------------------+-------------------------------+------------------------------- 2020-01-25 10:40:15.202836+00 | 2020-01-25 10:40:18.613984+00 | 2020-01-25 10:40:18.614083+00 (1 row) postgres=# SELECT current_timestamp,statement_timestamp(),clock_timestamp(); current_timestamp | statement_timestamp | clock_timestamp -------------------------------+-------------------------------+------------------------------- 2020-01-25 10:40:15.202836+00 | 2020-01-25 10:40:20.751923+00 | 2020-01-25 10:40:20.751981+00 (1 row) postgres=# SELECT current_timestamp,statement_timestamp(),clock_timestamp(); current_timestamp | statement_timestamp | clock_timestamp -------------------------------+-------------------------------+------------------------------- 2020-01-25 10:40:15.202836+00 | 2020-01-25 10:40:36.631681+00 | 2020-01-25 10:40:36.631779+00 (1 row)
age()
postgres=# select age('2020-01-01'::timestamp, timestamp '2000-01-01'); age ---------- 20 years (1 row)
- 引数は引き算の順番
- (to, from)
extract() / date_part()
postgres=# SELECT extract(month from now()); date_part ----------- 1 (1 row) postgres=# SELECT date_part('month', now()); date_part ----------- 1 (1 row)
- 日付から
minute
を抽出したりしようとすると、単に0を得る
date_trunc()
- 指定の精度に日付/時刻情報を切り捨てる
postgres=# SELECT date_trunc('minute', now()); date_trunc ------------------------ 2020-01-25 10:49:00+00 (1 row) postgres=# SELECT date_trunc('day', now()); date_trunc ------------------------ 2020-01-25 00:00:00+00 (1 row) postgres=# SELECT date_trunc('month', now()); date_trunc ------------------------ 2020-01-01 00:00:00+00 (1 row)
日付 / 時刻演算子
- 時刻に時間(interval)を足したりできるよ、という話
- 数値も足せる
postgres=# SELECT date('2020-01-01') + 1; SELECT date('2020-01-01') + 1; ?column? ------------ 2020-01-02 (1 row)
データ型書式設定関数
- キャストと異なり、書式も設定できる
to_char()
postgres=# SELECT to_char(now(), 'YYYYMMDD'); to_char ---------- 20200125 (1 row)
- 12時間/24時間
postgres=# SELECT to_char(timestamp '2020-01-23T23:45:12Z', 'HHMISS'); to_char --------- 114512 (1 row) postgres=# SELECT to_char(timestamp '2020-01-23T23:45:12Z', 'HHMISS'); postgres=# SELECT to_char(timestamp '2020-01-23T23:45:12Z', 'HH12MISS'); to_char --------- 114512 (1 row) postgres=# SELECT to_char(timestamp '2020-01-23T23:45:12Z', 'HH24MISS'); to_char --------- 234512 (1 row)
- hour
- HH,HH12: 12時間
- HH24: 24時間
- minute
MM
はmonthとかぶるのでMI
to_date() / to_timetamp()
postgres=# SELECT to_date('20000101', 'YYYYMMDD'); to_date ------------ 2000-01-01 (1 row)
to_number()
postgres=# SELECT to_number('42.195km', '99.999km'); to_number ----------- 42.195 (1 row)
- abd? de
OSS-DB試験勉強 - 黒本ch1 一般知識
一般知識
PostgreSQLのライセンス
https://www.postgresql.org/about/licence/
- PostgreSQLライセンス
- BSD, MITライク
Portions Copyright © 1996-2020, The PostgreSQL Global Development Group
Portions Copyright © 1994, The Regents of the University of California
Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
なんでGPLじゃないの
- 自分たちのライセンスを変えられたくないから
- 【補】GPLは伝染する
他のライセンスでリリースする予定
- 未定
- 永久にFOSSとする方針ではある
リレーショナルモデル
https://www.postgresql.org/docs/6.5/sql22234.htm
言葉の対応
リレーショナルモデル | PostgreSQL |
---|---|
リレーション | 表 |
属性 | 列、カラム、フィールド |
タプル | 行、レコード |
ドメイン | データ型 |
PostgreSQLの特徴
- カリフォルニア大学バークレー校CS学科で開発されたPOSTGRES v4.2ベース
- PostgreSQL 8.0からはWindowsでそのまま動く
- その前はCygwin等が必要だった
SQLコマンド大別
PostgreSQLがサポートしている/していない機能
https://www.postgresql.org/about/featurematrix/
クエリキャッシュはなさげ
Tablespace
https://www.postgresql.org/docs/current/sql-createtablespace.html
A tablespace allows superusers to define an alternative location on the file system where the data files containing database objects (such as tables and indexes) can reside.
A user with appropriate privileges can pass tablespace_name to CREATE DATABASE, CREATE TABLE, CREATE INDEX or ADD CONSTRAINT to have the data files for these objects stored within the specified tablespace.
- ファイルシステム上にテーブルスペースを作っておく
- データベースオブジェクトを作る際、配置するテーブルスペースを指定できる
- データベース
- テーブル
- インデックス
- ...
RDBに関する歴史
http://www.kogures.com/hitoshi/history/db-nenpyo/index.html
英語
- perpetuity
- 永久
Docker outside of Docker (DooD) & bind-mount で詰まった話 (原因解明済、未解決)
経緯
- GitLab private instanceを使っている案件にアサインした
- CIを構築するも、ランナー上でdockerを動かしたとき、bind-mountが意図通り動作しない
- 中身のあるディレクトリをマウントしたはずなのに、コンテナからは見えない
再現
ランナー相当のDooD構築
- ホストのdockerdのバージョンをまず調べる
host:~$ docker --version
Docker version 18.09.7, build 2d0083d
- コンテナ内のDockerクライアントから、コンテナ外(ホストマシン)のDockerデーモンにDocker リモートAPIを送信できるよう、ソケットをマウントする
- ホストのdockerdとバージョンの一致するdockerイメージを使用する
host:~$ docker container run --rm -dit -v /var/run/docker.sock:/var/run/docker.sock --name runner docker:18.09 host:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 71e6444211ef docker:18.09 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds runner
- この時点でこういう状態:
- 簡単のために、以下、ホストのDockerクライアントとRunnerコンテナのDockerデーモンを省く:
Runnerコンテナにログイン
host:~$ docker container exec -it runner sh
/ #
- Runnerコンテナ内でコンテナを一覧する
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 71e6444211ef docker:18.09 "docker-entrypoint.s…" 5 minutes ago Up 5 minutes runner
- Runnerコンテナ自身が見えている
- 外側(ホスト側)のDockerデーモンにDocker リモートAPIを送信できているからに他ならない
Runner内でコンテナ生成
# in Runner docker container run --rm -dit alpine sh docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0ff3670c39c1 alpine "sh" 3 seconds ago Up 2 seconds sad_mclean 71e6444211ef docker:18.09 "docker-entrypoint.s…" 22 minutes ago Up 22 minutes runner
ここまではふつう
Runner内でコンテナ生成する際、Runner内のファイルのbind-mountを試みる
- これになりたい:
- が、実際やってみるとおかしなことが起きる
- まずRunnerコンテナ内でファイルを用意
# in Runner mkdir /tmp/myapp echo 'index' > /tmp/myapp/index.html cat /tmp/myapp/index.html
index
- bind-mountしてコンテナ生成を試みる
# in Runner docker container run --rm -dit -v /tmp/myapp:/var/www/myapp busybox sh
41d34cf4052a...
- ログインしてファイルを見に行くと…
#in Runner docker container exec -it 41d34cf4052a sh # in new container ls /var/www/myapp -la
total 8 drwxr-xr-x 2 root root 4096 Jan 10 13:36 . drwxr-xr-x 1 root root 4096 Jan 10 13:36 ..
- ファイルがない
- ディレクトリはある
- 新しく生成されたコンテナ側の
/var/www/myapp/
でファイルをtouchしても、Runner側の/tmp/myapp/
には反映されない。 - mountできてないのでは…?
真相
- ホスト側のディレクトリがマウントされてました、というオチ
host:~$ ls /tmp/myapp -ld
drwxr-xr-x 2 root root 4096 Jan 10 22:36 /tmp/myapp
docker container run -v srcPath:destPath ...
のsrcPath
は、DockerクライアントではなくDockerデーモンから見たパスである
(考えてみれば当然。相対パスが使えないのもそれが理由だろうし)
世の中ではこの問題をどうやって解決しているんだろうか…
Clean Code ch8 Boundaries
- Boundaries
- Using Third-Party Code
- Exploring and Learning Boundaries
- Learning Log4j
- Learning Tests Are Better Than Free
- Using Code That Does Not Yet Exist
- Clean Boundaries
- 英語
Boundaries
- 全てのコードを自分たちの支配下に置けるとは限らない
- これらの「外部のコード」を自分たちのコードに綺麗に組み込まなければならない
- 本章では、クリーンな境界を維持する実践とテクニックに目を向ける
Using Third-Party Code
- インタフェースの綱引き
- サードパーティ: 広く適用できるインタフェースを追求する
- 多くの環境で幅広いユーザに売り込みたいから
- 利用側: ニーズ特化のインタフェースを求める
- サードパーティ: 広く適用できるインタフェースを追求する
- ライブラリの生の
Map
などを直接引き回すべきではない - なぜ
- 自前のクラス(boundary)に閉じ込めよう
- 【補】集約と委譲
Map
かMap<T>
か、といった実装の詳細は隠蔽される
Exploring and Learning Boundaries
- サードパーティAPIを学習するのは大変
- サードパーティAPIを自分たちのソフトウェアに組み込むのはもっと大変
- 同時にやろうとすると二重に大変
- サードパーティのコードを理解するためのテストを書こう
- サードパーティのコードをテストするのは自分たちの仕事ではない
- が、利用する部分を理解するためにテストを書き起こす
- Jim Newkirk はこの類のテストを「learning tests」と呼んでいる
Learning Log4j
- バグか、少なくとも一貫性のない仕様のあるログライブラリを学習する例
- 躓きながらドキュメントを読んだりググったりして調べていく
- 得た知識を単体テストに書き起こす
- 最終的に自前のロガークラス(boundary)にカプセル化する
Learning Tests Are Better Than Free
- learning testsを書いた後で振り返ってみると、余計なコストはかからなかったことがわかる
- サードパーティコードの学習はどのみち行わなければならなかった
- 知識を得るうえで、learning testsは簡単で隔離された方法だった
- 理解を進めるうえで、learning testsは正確な実験だった
- 【補】コードとして実行可能・再現性があるという意味合いかな
- さらにポジティブな見返りもある
- learning testsを書かなかったとしても、結局boundaryクラスのoutboundのテストは書くべき
- さもないと怖くてサードパーティコードをアップデートできなくなりがち
Using Code That Does Not Yet Exist
- わかっているところ(自分たちの世界)と不明なところ(新しい世界)とを分離する
- 例: 分業していて、依存モジュール待ちの場合
- Adapter Pattern (GoF)を使え
- 作業がブロックしないようにinterfaceだけ切っておく
- このinterfaceはテストにも有用
- Fakeを使ってクライアントコードをテストできる
- 【補】Service Stub (PofEAA)
Clean Boundaries
英語
- be inhibited from doing
- ...するのを(心理的に)抑制された
- 変更箇所が甚大で触りたくない、みたいな
- ...するのを(心理的に)抑制された
- off the edge
- 正気を失って
- accommodate
- 余地がある
- lest
- ...するといけないから
Clean Code ch7 Error Handling
- Error Handling
- Use Exception Rather Than Return Codes
- Write Your Try-Catch-Finally Statement First
- Use Unchecked Exceptions
- Provide Context With Exception
- Define Exception Classes in Terms of a Caller's Needs
- Define the Normal Flow
- Don't Return Null
- Don't Pass Null
Error Handling
- エラーハンドリングは重要
- だが、エラーハンドリングで散らかって、ロジックの理解が困難になりがち
- 本章ではクリーンで堅牢なコードを書くためのテクニックを紹介する
Use Exception Rather Than Return Codes
- 例外機構がなかったのは遠い昔の話
- 当時はエラーハンドリング・レポート方法は限られていた
- エラーフラグをセットする
- エラーコードを返す
- これらの方法では、呼び出し側がその場ですぐにチェックしなければならない
- ロジックが散らかる
- チェックし忘れる
- 【補】
printf
の戻り値をちゃんとチェックする人が何人いますか?みたいな話
- 【補】
- 例外を投げよう
- 離れたところでチェックできる
- 関心の分離
- ロジック
- エラーハンドリング
Write Your Try-Catch-Finally
Statement First
- 例外機構はスコープを定義する
- トランザクションのようなもの
- tryブロック中の任意の場所で処理が中断されうる
- 中断された場合、catchブロックから処理が再開する
- catchブロックがconsistencyを担保する
- 例外を投げうるコードを書くときは、(中身ではなく)
try-catch-finally
ステートメントから書き始めよう- tryブロック中で例外が発生したとき、利用側が何を期待すべきかをまず決める
- TDD的には、まず「例外が投げられる」テストケースから書き始める
- red: 例外を投げないstub実装
- green: 例外を投げる実装
- refactoring: 例外の型を縮小するなど
Exception
からFileNotFoundException
にするとか
- その後でtryの中身を作っていく
- 普段通りのTDD
- さも例外が発生しえないかのように扱える
Use Unchecked Exceptions
- 検査例外
- 本当に「値段に見合う価値」があるのか?
- 検査例外における「値段」とは何か
- OCP違反
- 下位モジュールが検査例外を投げるように変更したとする
- catchされるまでの上位のモジュールすべてが影響を受ける
- throws句の追加・再デプロイ
- 例外機構の本末転倒
- 呼び出し側がその場ですぐチェックしなくて済むように導入したはずなのに
- 検査例外は、クリティカルなライブラリを書くうえで有用なこともある
- が、一般的なアプリケーション開発では依存のコストのほうが高くつく
Provide Context With Exception
- 例外に情報を詰めよう
- エラー発生源・場所
- 失敗した操作の意図
- 失敗の種類
- catchブロックでログ出力できうる十分な情報
Define Exception Classes in Terms of a Caller's Needs
- catch側のことを考えて例外を分類しよう
- あるものをcatchして別のものを素通りさせたい、というときにだけ例外クラスを分けよう
Define the Normal Flow
- 必要に応じてSpecial Caseを使おう
- PofEAAのやつ
- 【補】NullObjectはSpecial Caseの特別なケース(special case)
Don't Return Null
- nullチェックまみれになる
- 一つ忘れるとNullPointerException
- 代わりに例外を投げるか、空のリスト、NullObjectなどを返そう
- 【補】Maybe, Eitherモナドなんかもあるわね
- パターンマッチがないと辛い
Don't Pass Null
- メソッドにnullを渡すのは、nullを返すよりももっと悪い
- 渡された側はどうする
- 何もしない
- 実行時にNullPointerExceptionが発生しうる
- nullチェックしてInvalidArgumentExceptionを投げる
- NullPointerExceptionよりは幾分マシ
- だが、InvalidArgumentExceptionのハンドラが必要
- 捕まえてどうするの?
- 非nullアサート
- よいドキュメンテーションにはなる
- が、依然として実行時エラーが発生しうることは変わらない
- 何もしない
- nullを渡すのを禁止するのが吉
- 【補】「参照型でもnullを禁止してくれる」言語だと考えなくていいので嬉しいですね
- C#8.0の目玉機能