memo.log

技術情報の雑なメモ

『オブジェクト指向設計実践ガイド』3章メモ

テーマ

  • 依存関係の少ない(疎結合な、変更に強い)コードを書く

抑えておきたいこと

具象クラスは、抽象クラスよりも変わる可能性が高い

.

この概念については、「依存オブジェクトの注入」で一度取り上げました。そこでGearが依存していたのは、WheelとWheel.new、そしてWheel.new(rim, tire)でした。極端に具象的なコードに依存していたと言えるでしょう。しかし、コードを変更したあと、つまり、WheelがGearに注入されるようになったあとではどうでしょうか。Gearはとたんに、何かもっと抽象的なものに依存するようになりました。dimeterメッセージに応答できるオブジェクトにアクセスするようになったという事実がそれです。 Rubyに親しんでいると、このような遷移は、当然のように思えるかもしれません。しかし、少し立ち止まって考えてみましょう。同じ対策を、静的型付言語で実現するとすれば、何が必要になったでしょうか。静的型付言語はコンパイラを持ち、そのコンパイラは型に対するユニットテストのような役割を果たします。そのため、単に適当なオブジェクトをGearに注入するわけにはいきません。代わりに「インターフェース」を宣言する必要があるでしょう。diameterをインターフェースの一部として定義し、インターフェースをWheelクラスにインクルードします。その後、注入しようとしているクラスが、そのインターフェースの「一種」だとGearに教えるのです。

.

Wheel をGearへ注入することで、Gearがdeameterに応答するダックタイプに依存するように変える時、実は、さりげなくインターフェースを定義しているのです。このインターフェースは、あるカテゴリーのものはdiameterを持つ、という概念が抽象化されたものです。抽象が、具体クラスから収穫されました。

.

本質的に、抽象はより安定しています。Rubyではインターフェースを定義するために明示的な抽象を宣言する必要はありません。しかし、設計の目的のためなら、仮想的なインターフェースがクラス同様に現実に存在するものであると考えて構いません。

メモ

一応最近自分で書いたコードだと、以下部分はダックタイピング的な感じで書いたと思う(ほぼ無意識だったが。。) header は dump というメソッド(データを表示するふるまい)を持つオブジェクトなら任意であり、このクラスの外から注入される。

      @headers.each do |header|
        header.dump
      end

simple_capture/packet.rb at d1001b2dea56e2807e0257609273d5a650faba82 · kuredev/simple_capture · GitHub

コードによるメモ

以下のコードの問題点は、

  • Gear の Wheel に対する依存が多い
    • クラスの名前、メソッドの名前、オブジェクトの作り方、メソッドの呼び方
  • Wheelの変更によって、Gearの変更が強制される可能性が高い
class Gear
  attr_reader :chainring, :cog, :rim, :tire

  def initialize(chainring, cog, rim, tire)
    @chainring = chainring
    @cog = cog
    @rim = rim
    @tire = tire
  end

  def ratio
    chainring / cog.to_f
  end

  def gear_inches
    ratio * Wheel.new(rim, tire).diameter
  end
end

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim = rim
    @tire = tire
  end

  def diameter
    rim + (tire * 2)
  end
end

puts Gear.new(52, 11, 26, 1.5).gear_inches
  • 依存オブジェクトの注入を行う
  • 以下のように、Wheel インスタンスの作成をGearの外に移動する
  • これにより、Gearはdiameter を実装するオブジェクトであればどれとでも共同作業ができるようになる
    • Gearはdiameter メソッドへの依存1つを残すのみとなって、GearはWheelに対する知識を減らすことができた
class Gear
  attr_reader :chainring, :cog, :wheel

  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog = cog
    @wheel = wheel
  end

  def ratio
    chainring / cog.to_f
  end

  def gear_inches
    # ダックタイピング
    # diamter メソッドを喋れるオブジェクトなら何でもOK
    #  これを依存オブジェクトの注入
    #  依存は削減され、diamter メソッドへの依存1つを残すのみとなった
    ratio * wheel.diameter
  end
end

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim = rim
    @tire = tire
  end

  def diameter
    rim + (tire * 2)
  end
end

# Gear は diameter を知るDuckを要求する
puts Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches

外部へのメッセージを隔離する

class Gear
  attr_reader :chainring, :cog, :wheel

  def initialize(chainring, cog, wheel)
    @chainring = chainring
    @cog = cog
    @wheel = wheel
  end

  def ratio
    chainring / cog.to_f
  end

  def gear_inches
    ratio * diameter
  end

  # 外部へのメッセージ(外部のクラスのデータやメソッドを参照する)は出来るだけ独立させる
  #  変更が発生しやすい箇所のため
  # `外部メソッドならどれでもこのように前もって隔離する対処をできるというわけではありませんが`
  # `それでも自分のコードを調査する価値はあるでしょう。最も脆い依存を探しだし、包み隠しましょう`
  def diameter
    wheel.diameter
  end
end

class Wheel
  attr_reader :rim, :tire
  def initialize(rim, tire)
    @rim = rim
    @tire = tire
  end

  def diameter
    rim + (tire * 2)
  end
end

# Gear は diameter を知るDuckを要求する
puts Gear.new(52, 11, Wheel.new(26, 1.5)).gear_inches

参考リンク

Rubyにおける依存性の注入 - masaki's note https://scrapbox.io/masakis-note/Ruby%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5 →分かりやすい。 要は依存しているオブジェクトをクラスの中で生成するのではなく、期待しているふるまいを持つものを外から入れるようにすればいいと考えれば良さそう。

Docker と Nginx でリバースプロキシを構成する最低限のメモ

前提

  • 構成は以下のイメージ
    • ホストで80番ポートで受ける、DockerのNginxは80番ポートで受ける、後ろに80番ポートで流す
  • docker はインストール済であること、後ろのサーバは動いていること
  • Amazon Linux2
    • Linux ip-172-31-4-104.ap-northeast-1.compute.internal 4.14.219-161.340.amzn2.aarch64

f:id:kuredev:20210222195409p:plain

手順メモ

①Nginx設定ファイル作成

素のNginxの設定ファイルから持ってきつつ、リバプロっぽい設定を入れた。 ポイントは include /etc/nginx/conf.d/*.conf; を削除したところだったぽい。 もともとDockerでNginxを起動するとコンテナ内でこの /etc/nginx/conf.d/*.conf; ディレクトリに設定ファイルが合ってそちらも読み込まれてしまうとうまくリバプロとして動かなかった。

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

        location /test {
            proxy_pass http://[後ろのサーバのIPアドレス]/;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

②Dockerファイル作成

[ec2-user@ip-172-31-4-104 docker]$ pwd
/home/ec2-user/docker
[ec2-user@ip-172-31-4-104 docker]$ ls
Dockerfile  nginx.conf
[ec2-user@ip-172-31-4-104 docker]$ cat Dockerfile
FROM nginx:latest

ADD ./nginx.conf /etc/nginx/nginx.conf

③ Docker のイメージ作成、コンテナ作成、コンテナ起動

$ sudo docker build -t nginx-img ./
$ sudo docker create --name nginx-container -p 80:80 -it [Image ID]
$ sudo docker start [Container ID]

※③' docker-composer でコンテナを実行する場合

以下のように設定ファイルを準備し、実行

[ec2-user@ip-172-31-4-104 docker]$ cat docker-compose.yml
version: '3'
services:
  nginx:
    build: .
    ports:
     - "80:80"
[ec2-user@ip-172-31-4-104 docker]$ sudo docker-compose up

④動作確認

外からDocker/Nginxのサーバに向けて

http://[IP Addr]/test

で後ろのサーバのコンテンツが返ってくるはず。

参考

nginx https://hub.docker.com/_/nginx

【Docker】Nginxのconfで環境変数を使う - Qiita https://qiita.com/jungissei/items/2d6b40320b520f52b502

Compose を始めましょう — Docker-docs-ja 19.03 ドキュメント https://docs.docker.jp/compose/gettingstarted.html#dockerfile

【Ruby】1文字(1Byte)の4bit分の数値を取得する

>  80.to_s(2)
=> "1010000"
> 80[0, 4]
=> 0
> 80[4, 4]
=> 5

参考

プロと読み解くRuby 2.7 NEWS - クックパッド開発者ブログ https://techlife.cookpad.com/entry/2019/12/25/121834

【Ruby】数値と文字列の変換メモ

文字列→数値

基本形 
> "10".to_i
=> 10

整数とみなせない文字があればそこまでを変換対象とします。変換対象が空文字列であれば 0 を返します。
> "a".to_i 
=> 0

基数の指定が可能(レシーバの文字列を基数の数値と解釈して返却する)
> "10".to_i(2) 
=> 2 
> "10".to_i(16) 
=> 16
> "0x10".to_i(0) 
=> 16

数値→文字列

0 - 31 の制御文字の場合は表現できないので、16進数で表示されるみたい ord で戻せる

そのまま文字にする
irb(main):068:0> 1.to_s 
=> "1" 
irb(main):069:0> 01.to_s 
=> "1" 
irb(main):070:0> 0x10.to_s 
=> "16"
> 10.to_s(16)
=> "a"

エンコーディング
irb(main):054:0> 1.chr 
=> "\x01" 
irb(main):055:0> 65.chr 
=> "A" 
irb(main):056:0> 65.chr.encoding 
=> #<Encoding:US-ASCII> 
irb(main):057:0> 64.chr.encoding 
=> #<Encoding:US-ASCII> 
irb(main):058:0> 64.chr 
=> "@" 
irb(main):059:0> 63.chr 
=> "?" 
irb(main):060:0> 48.chr 
=> "0" 
irb(main):061:0> 31.chr 
=> "\x1F"
> 63.chr.ord 
=> 63
> p 10.chr Encoding::ASCII_8BIT # 文字コード指定
"\n"
irb(main):080:0> "kkk".length
=> 3
irb(main):081:0> "\x12\x12".length
=> 2
> "\x65\x01\x41".chars 
=> ["e", "\u0001", "A"]
irb(main):085:0> "kkk".chars 
=> ["k", "k", "k"] 
irb(main):086:0> "\x65".chars 
=> ["e"] 
irb(main):087:0> "\x65\x01".chars 
=> ["e", "\u0001"] 
irb(main):088:0> "\x65\x01\x64".chars 
=> ["e", "\u0001", "d"] 
irb(main):089:0> "\x65\x01\x14".chars 
=> ["e", "\u0001", "\u0014"] 
irb(main):090:0> "\x65\x01\x41".chars 
=> ["e", "\u0001", "A"]

irb(main):042:0> 10.chr
=> "\n"
irb(main):043:0> 10.chr.ord
=> 10
irb(main):044:0> 10.chr.ord.chr
=> "\n"
irb(main):045:0> 10.chr.ord.to_s
=> "10"
irb(main):046:0> 10.chr.ord.to_s(16)
=> "a"

irb(main):053:0> 121.chr
=> "y"
irb(main):054:0> 0x79.chr
=> "y"

参考

String#to_i (Ruby 3.0.0 リファレンスマニュアル)

【Linux】ネットワークインターフェースの番号(インデックス)を確認するコマンド

コマンド自体は極めてオーソドックス。 ip link show で表示される番号が該当してるっぽい。

 % ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen
 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default ql
en 1000
    link/ether 0a:16:53:d0:fc:80 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default ql
en 1000
    link/ether 0a:2b:38:3d:bb:0a brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default ql
en 1000
    link/ether 0a:aa:c9:62:6e:fa brd ff:ff:ff:ff:ff:ff
5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT grou
p default 
    link/ether 02:42:dc:ed:12:78 brd ff:ff:ff:ff:ff:ff
6: br-5ec1c975db31: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFA
ULT group default 
    link/ether 02:42:f9:7d:c3:56 brd ff:ff:ff:ff:ff:ff

参考

sysfs - Is it possible to get network interface index not via /sys? - Unix & Linux Stack Exchange https://unix.stackexchange.com/questions/150216/is-it-possible-to-get-network-interface-index-not-via-sys