勉強日記

チラ裏

OSS-DB試験勉強 - 黒本ch2 インストールと設定


PostgreSQL公式チュートリアル: Installation from Source Code

https://www.postgresql.org/docs/12/installation.html

環境を汚したくないのでコンテナで遊ぶ

github.com

ビルド環境絡み

  • 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認証を行う場合に使用する
  • --with-perl
  • --with-python

変遷まとめ記事。ありがたい

https://qiita.com/nuko_yokohama/items/e2d479a1af0de6cc50a6

gmakeコマンドに関して

https://github.com/postgres/postgres/blob/master/GNUmakefile.in

  • make or make 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コマンドが必要だった
/$ 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)
    • *だと任意のIPアドレスをリッスン
    • 0.0.0.0だと任意のIPv4アドレス
    • ::だと任意のIPv6アドレス
    • 空だと一切リッスンしない
    • デフォルトlocalhost
      • ループバックのみ許可
    • 悪意のあるコネクション要求が繰り返されるのを防止するために使用する
      • cf. クライアント側の認証は細やかなアクセス制御に使用する
  • port (integer)
    • TCPポート番号(5432)
  • max_connections (integer)
  • superuser_reserved_connections (integer)
    • スーパーユーザのために予約する接続数(3)
      • max_connectionsが100、本パラメータが3なら、一般ユーザは97接続まで

リソース、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

www.oreilly.com


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にすることも検討する
  • 高水準のアルゴリズムの重複: Template Method Patternを適用する

Expressive

  • 長い長い運用のコストを下げるために
  • 良い命名
  • 関数やクラスを小さくする
    • 命名しやすい
    • 書きやすい
    • 理解しやすい
  • 標準の命名法を使用する
  • よくできた単体テスト
  • 一番重要なのは、気遣い
    • 【補】try の意訳
    • 次にそのコードを読む人が読みやすくなるように
      • たいてい自分自身ですよ

Minimal Classes and Methods

  • クラスやメソッドを小さくしようとしすぎると、数が増えすぎる問題
  • システムのサイズも小さくするために、クラスや関数の数は最小限に
    • ただし先述の3ルールよりは優先度低い
  • 【補】A Philosophy of Software Design でもこのことには触れられている
    • モジュールのインタフェースの数が増えるということは、学習コスト = 認知の負荷が増え、複雑性が増すということ

英語

OSS-DB試験対策 緑本 ch9 組み込み関数と演算子

www.shoeisha.co.jp


集約関数

しってるから略

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

「~」演算子

  • 部分一致検索用
一致で真 不一致で真
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)
  • statement_timestamp(): SQL開始時点の日付/時刻
  • clock_timestamp(): この関数が実行された時点の日付/時刻
  • ほかの: トランザクション開始時点の日付/時刻

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)

  1. abd? de

OSS-DB試験勉強 - 黒本ch1 一般知識

一般知識


PostgreSQLのライセンス

https://www.postgresql.org/about/licence/

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の特徴

SQLコマンド大別

  • DDL
    • CREATE TABLEとか
  • DML
    • SELECTとか
  • DCL

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

  • SEQUEL: IBM, SQLの前身
  • Ingres: Michael Stonebraker, Postgresの前身
    • Post + Ingres

英語

  • 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
  • この時点でこういう状態:

f:id:wand_ta:20200110222115p:plain

  • 簡単のために、以下、ホストのDockerクライアントとRunnerコンテナのDockerデーモンを省く:

f:id:wand_ta:20200110222257p:plain

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を送信できているからに他ならない

f:id:wand_ta:20200110222436p:plain

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

f:id:wand_ta:20200110222519p:plain

ここまではふつう

Runner内でコンテナ生成する際、Runner内のファイルのbind-mountを試みる

  • これになりたい:

f:id:wand_ta:20200110222759p:plain

  • が、実際やってみるとおかしなことが起きる
  • まず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 ..

f:id:wand_ta:20200110224215p:plain

  • 新しく生成されたコンテナ側の/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

f:id:wand_ta:20200110223422p:plain

docker container run -v srcPath:destPath ...

srcPathは、DockerクライアントではなくDockerデーモンから見たパスである

(考えてみれば当然。相対パスが使えないのもそれが理由だろうし)

世の中ではこの問題をどうやって解決しているんだろうか…

Clean Code ch8 Boundaries

www.oreilly.com


Boundaries

  • 全てのコードを自分たちの支配下に置けるとは限らない
  • これらの「外部のコード」を自分たちのコードに綺麗に組み込まなければならない
  • 本章では、クリーンな境界を維持する実践とテクニックに目を向ける

Using Third-Party Code

  • インタフェースの綱引き
    • サードパーティ: 広く適用できるインタフェースを追求する
      • 多くの環境で幅広いユーザに売り込みたいから
    • 利用側: ニーズ特化のインタフェースを求める
  • ライブラリの生のMapなどを直接引き回すべきではない
  • なぜ
    • 必要以上の機能が使えてしまう
      • ロジックの意図としては要素を削除される想定がないのにclear()メソッドが生えていたり
    • Javaの非ジェネリクス時代のMapの場合、利用側がダウンキャストまみれになる
    • Mapのインタフェースが変更されたら変更箇所甚大になる
      • そんな変更はまずないだろうって?
      • Java5でジェネリクスが追加されましたよね
  • 自前のクラス(boundary)に閉じ込めよう
    • 【補】集約と委譲
    • MapMap<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は正確な実験だった
      • 【補】コードとして実行可能・再現性があるという意味合いかな
  • さらにポジティブな見返りもある
    • サードパーティコードは、不具合修正や機能追加により、振る舞いが我々の意図せぬものに変わっている可能性がある
      • 【補】Laravelで、コントローラのコンストラクタの中で\Auth::user()が取れなくなったりしましたね
    • leaning 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

www.oreilly.com


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

  • 検査例外
    • Javaの最初のバージョンで導入され、素晴らしいアイデア…だと思われていたもの
    • メソッドのシグネチャに例外を含める
  • 本当に「値段に見合う価値」があるのか?
  • 検査例外における「値段」とは何か
  • 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を返すよりももっと悪い
  • 渡された側はどうする
    • 何もしない
    • nullチェックしてInvalidArgumentExceptionを投げる
      • NullPointerExceptionよりは幾分マシ
      • だが、InvalidArgumentExceptionのハンドラが必要
        • 捕まえてどうするの?
    • 非nullアサート
  • nullを渡すのを禁止するのが吉
  • 【補】「参照型でもnullを禁止してくれる」言語だと考えなくていいので嬉しいですね
    • C#8.0の目玉機能