さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

インフラエンジニアの教科書読んでみた

インフラエンジニアの教科書を読んでみました。

www.c-r.com

せっかくなので個人的なレビューをしてみます。

ざっくりどんな本か

  • インフラエンジニアの仕事の種類
  • インフラエンジニアの基礎知識

といったものが記載されています。

良かった点

インフラの基礎知識、通信の知識など非常に重要な箇所をざっくりと網羅しています。
インフラってなんだろう?って状態で読むと、どんな概念があるといったとっかかりを得るのに非常に役に立つでしょう

僕も5年程度エンジニアをしているのですが、そういえばふわっとしか認識してなかった。。。みたいな知識はちょくちょく出てきました。
学び直す良いきっかけを得ることができました。

悩ましかった点

すべての記載が非常にざっくりで詳細はほぼ一切記載されていないことです。
この本に記載されている知識はこの本を読んでおけばOKというより、この本でその知識があるのを知るといった感じですね。
悪い点かと言われれば微妙な感じです。そういう目的の本なので

また、IntelCPUの種類一覧など載っていましたが、これは載っていてもすぐ古くなるので。。。みたいなのが散見されました。

こういう人に向いている

インフラのことを何も知らない人向けです。
ちょっとエンジニアやってたら勉強せざるを得ない必要な概念の詰め合わせといったところですね。
数年エンジニアをやっている方が学び直すためというのにはおそらく不十分でしょう。 インフラを何も知らない方は何がわからないかわからないといった方が多いでしょう。そういう方には神のような本だと思います。

Pythonで学ぶデザインパターン入門 Factory Methodパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第4段です。

Factory Methodパターンとは

前回紹介した

saruhei1989.hatenablog.com

を、インスタンス生成の際の肉付けという特定パターンで活用したものです。
つまり、サブクラスでインスタンスを生成します。

ソースコード

コードの概要

ざっくりと言うとIDカードを作成するといったコードになります。
スーパークラスに、frameworkパッケージとしてインスタンス生成を担当する Factoryクラス と生成される Product クラスが存在します。
それぞれにスーパークラスをIDカードにおける肉付けをして継承したクラスがidcardパッケージとして存在します。

ソースコード

Product

# coding: utf-8
from abc import ABCMeta, abstractmethod


class Product(metaclass=ABCMeta):

    @abstractmethod
    def use(self):
        pass
Factory
# coding: utf-8
from abc import ABCMeta, abstractmethod

from factory_method.framework.product import Product


class Factory(metaclass=ABCMeta):

    def create(self, owner: str) -> Product:
        p = self.create_product(owner)
        self.register_product(p)
        return p

    @abstractmethod
    def create_product(self, owner: str) -> Product:
        pass

    @abstractmethod
    def register_product(self, product: Product):
        pass

インスタンスを返すメソッドだけ決め打ちで処理を生成してしまって、あとはサブクラスに任せます。

IDCard (Productを継承)

# coding: utf-8
from factory_method.framework.product import Product


class IDCard(Product):

    def __init__(self, owner: str):
        print('%sのカードを作ります。' % owner)
        self.__owner = owner

    def use(self):
        print('%sのカードを使います。' % self.__owner)

    def get_owner(self) -> str:
        return self.__owner

IDCardFactory(Factoryを継承)

# coding: utf-8
from factory_method.framework.factory import Factory
from factory_method.framework.product import Product
from factory_method.idcard.id_card import IDCard


class IDCardFactory(Factory):

    def __init__(self):
        self.__owners = []

    def create_product(self, owner: str) -> Product:
        return IDCard(owner)

    def register_product(self, product: Product):
        product.__class__ = IDCard
        self.__owners.append(product.get_owner())

    def get_owners(self) -> list:
        return self.__owners

Factoryを継承したこのサブクラスで具体的にどのクラスのインスタンスを作成するかを指定します。

実行ファイル

# coding: utf-8
from factory_method.idcard.id_card_factory import IDCardFactory

if __name__ == '__main__':
    factory = IDCardFactory()
    card1 = factory.create('さるへい')
    card2 = factory.create('へいさる')
    card3 = factory.create('たかし')
    card1.use()
    card2.use()
    card3.use()

実際どう便利か

ポイントは、Factoryはサブクラスで具体的にどのインスタンスが生成されるかはわからないということです。
Factoryクラスはサブクラスの存在を一切気にする必要がありません。よってこの抽象クラスを使い回すことができます。

インスタンス生成の定形パターンがあった際などは、Factory Methodパターンを用いてコードの重複を排除できたりするのでおすすめです。

クリーンアーキテクチャ(Clean Architecture)読んでみた

最近巷で有名なClean Architectureを読んでみました。

Clean Architecture - アスキードワンゴ

ものすごく僕個人の気持ちですが、まとめてみました。

ざっくりどんな本か

といったことをアンチパターンと一緒に解説している本です。

良かった点

  • オブジェクト指向や様々な原則の復習になった
    • それがわからないとこの本の内容がわからないため
  • 納得感のある話だった。

この本は、様々な原則に則ってかかれてあり、だいたいの理屈には納得感があります。
そもそも何が良いものか?みたいなものを理解するために オープン・クローズドの原則 など、様々な原則の復習が必要です。
この本にはその解説も併記されているのでその点ではわかりやすくかつとても復習になります。

悩ましかった点

  • あまりにも断定口調
  • 筆者の癖が強そう

この本の記述は、この理屈こそ正義みたいな感じが強いです。
この理屈から外れたら悪!みたいな断定が多く、過去のアンチパターンの紹介とともに、あんなアンチパターンを選択したあいつらは理解出来ない!みたいな記載がとても多いです。
ビジネスの都合場、一旦アンチパターンを選択せざるを得ないことは往々にしてあるでしょう。それすら悪と断定されてしまうのは微妙な気がします。

まとめ

とても断定口調なところ以外はとても良い本だと思います。
わかりやすい本なのでじっくり読んでみて、よく考えながら飲み込むと良いのではないかなぁと思います。
逆に自分があんまりない方だと流されていって良くない未来が見えそうな感じもします。
癖の強さまで継承されてしまうと、正直とても仕事上扱いづらい方そうになりそうだなぁという気持ちが強いですね。

Pythonで学ぶデザインパターン入門 Template Methodパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第3段です。

Template Method パターンとは

文字通り、処理の大枠のテンプレートを作るパターンです。
具体的には、抽象クラスなどで処理のテンプレートを作成し、その詳細な実装は継承したクラスに任せるといったものになります。

ソースコード

コードの概要

「文字や文字列を5回繰り返して表示する」プログラムです。
抽象クラスである AbstractDisplaydisplay メソッドが定義されており、それを叶えるために3つのメソッドが定義されています。
そのメソッドの中身は何も定義されておらず、その処理をサブクラスに任せるといったものになります。

つまり、その処理次第で多種多様な display メソッドが構築できるといったことになります。

ソースコード

AbstractDisplay(処理のテンプレートが記載してある抽象クラス)

# coding: utf-8
from abc import ABCMeta, abstractmethod


class AbstractDisplay(metaclass=ABCMeta):

    @abstractmethod
    def open(self):
        pass

    @abstractmethod
    def print(self):
        pass

    @abstractmethod
    def close(self):
        pass

    def display(self):
        self.open()

        for i in range(5):
            self.print()

        self.close()

概要通りのコードですね。
display メソッドだけが具体的に記載されています。
それ以外のメソッドをサブクラスで実装しましょう。

CharDisplay(文字単体をdisplayする)

# coding: utf-8
from template_method.abstract_display import AbstractDisplay


class CharDisplay(AbstractDisplay):

    def __init__(self, ch: str):
        if len(ch) > 1:
            raise TypeError('文字は一文字')
        self.__ch = ch

    def open(self):
        print('<<', end='')

    def print(self):
        print(self.__ch, end='')

    def close(self):
        print('>>')

H を入力としたら <<HHHHH>> といった出力があることを期待するものとなります。
抽象クラスのものを素朴に継承してるだけですね。
Pythonのprintは勝手に改行されてしまうので、改行しないようには一工夫必要です。

StringDisplay(文字列をdisplayする)

# coding: utf-8
from template_method.abstract_display import AbstractDisplay


class StringDisplay(AbstractDisplay):

    def __init__(self, string: str):
        self.__string = string
        self.__width = len(string.encode('utf-8'))

    def open(self):
        self.print_line()

    def print(self):
        print('|%s|' % self.__string)

    def close(self):
        self.print_line()

    def print_line(self):
        print('+', end='')
        for i in range(self.__width):
            print('-', end='')
        print('+')

Hello World. を入力とすると

+------------+
|Hello World.|
|Hello World.|
|Hello World.|
|Hello World.|
|Hello World.|
+------------+

を期待するものです。
ここまでいじくってても、 open close print の3メソッドがあれば成り立ちます。

実行ファイル

# coding: utf-8
from template_method.char_display import CharDisplay
from template_method.string_display import StringDisplay

if __name__ == '__main__':
    d1 = CharDisplay('H')
    d2 = StringDisplay('Hello World.')
    d3 = StringDisplay('こんにちわ。')

    d1.display()
    d2.display()
    d3.display()

実際どう便利か

このパターンは簡単ですが、便利な図を想像しづらいかもしれません。
例えば、このソースコードのパターンで画像で同じ出力が欲しくなった時に上記の通りに実装するといった方針が立てられます。
その上で同じスーパークラスを継承してるので、型の制限もしやすいといったメリットもあります。

紹介したソースコードは簡単なロジックでしたが、もっと複雑になってくるとロジックがテンプレートとして用意されていることは可読性に非常に効いてくるので、とても便利ですよ。

Python3.7入れる時のOpenSSLエラー

以下の記事に続き、Python3.6まで大丈夫だけど、Python3.7インストール時にopensslエラーがでました

saruhei1989.hatenablog.com

エラーがでたら以下のような表示になります。

$ pipenv --python 3.7.2
Warning: Python 3.7.2 was not found on your system…
Would you like us to install CPython 3.7.2 with pyenv? [Y/n]: Y
Installing CPython 3.7.2 with pyenv (this may take a few minutes)…
✘ Failed...
Something went wrong…
Downloading Python-3.7.2.tar.xz...
-> https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tar.xz
Installing Python-3.7.2...
ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

Please consult to the Wiki page to fix the problem.
https://github.com/pyenv/pyenv/wiki/Common-build-problems


BUILD FAILED (CentOS Linux 7 using python-build 20180424)

Inspect or clean up the working tree at /tmp/python-build.20190410073345.28295
Results logged to /tmp/python-build.20190410073345.28295.log

Last 10 log lines:
        esac; \
         ./python -E -m ensurepip \
                $ensurepip --root=/ ; \
fi
Looking in links: /tmp/tmpxo6kc0qg
Collecting setuptools
Collecting pip
Installing collected packages: setuptools, pip
Successfully installed pip-18.1 setuptools-40.6.2
make[1]: Leaving directory `/tmp/python-build.20190410073345.28295/Python-3.7.2'

対処法としては以下のようにopensslのアップデートをすればOKです。

$ yum update openssl

やっぱりひっかかちゃいましたね。

Pythonで学ぶデザインパターン入門 Adapterパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第2段です。

Adapterパターンとは

adapterパターンとは、その名の通り電源に対する電源アダプターのポジションです。
「すでに提供されているもの」がそのまま使えない場合、それに対して上からかぶせて出力を変換するようなものですね。

ソースコード

コードの概要

ここでは、 Print 抽象クラスとして必要な機能が定義されています。それを Banner クラスの文字を整形して出力するという機能に対して、PrintBanner クラスでかぶせて必要な機能を作成するといったコードです。 つまり、仕様が Print クラスで、電源が Banner クラスで、電源アダプターが PrintBanner クラスです。 Banner クラスでは、

  • 文字列を " ではさんで表示する
  • 文字列を * ではさんで表示する

の2種類の機能が用意されており、それぞれが Print クラスに対して弱く出力する、強調して出力するに該当する出力となります。

Adapterパターンには、

  • 継承をつかったパターン
  • 移譲を使ったパターン

の2種類があるのでその両方のコードを紹介します。移譲は、処理を別のところに任せるという意味になります。

ソースコード

Bannerクラス(もともと用意されたもの)

# coding: utf-8


class Banner(object):

    def __init__(self, string: str):
        self.__string = string

    def show_with_paren(self):
        print('"%s"' % self.__string)

    def show_with_aster(self):
        print('*%s*' % self.__string)

前述の通り、2種類の出力機能があります。

Printクラス (必要な機能の定義)

# coding: utf-8
from abc import ABCMeta


class Print(metaclass=ABCMeta):

    def print_week(self):
        pass

    def print_strong(self):
        pass

前述の通り、2種類の機能を必要としています。

javaだったら、継承の場合はinterface、移譲の場合は抽象クラスになります。
pythonはinterfaceがないので両方抽象クラスで表現しました。

PrintBanner クラス (adapter)

継承
# coding: utf-8
from adapter.inherit.banner import Banner
from adapter.inherit.print import Print


class PrintBanner(Banner, Print):

    def print_week(self):
        self.show_with_paren()

    def print_strong(self):
        self.show_with_aster()

継承の場合はただ継承しているだけですね。わかりやすいといえばわかりやすいです。
わかりやすい反面、制限が多いので継承はコードの自由度が低めなのでよく考えて使ったほうが良いでしょう。

移譲
# coding: utf-8
from adapter.delegation.banner import Banner
from adapter.delegation.print import Print


class PrintBanner(Print):

    def __init__(self, string: str):
        self.__banner = Banner(string)

    def print_week(self):
        self.__banner.show_with_paren()

    def print_strong(self):
        self.__banner.show_with_aster()

移譲は、コンストラクタでインスタンスを生成するなどしてそこに処理を移譲します。
継承する必要がないので、自由度は高いです。

実行ファイル

# coding: utf-8
from adapter.inherit.print_banner import PrintBanner

if __name__ == '__main__':
    p = PrintBanner('Hello')
    p.print_week()
    p.print_strong()

こちらは、継承と移譲でimportが違うのですが、些細な違いなので片方だけ記載します。

実際どう便利か

めちゃめちゃ便利です。
よく考えて使わないと複雑になりがちなのですが、既存の機能に修正をいれるなどをせずに追加実装ができたりするので、とても便利です。
オープン、クローズドの原則を叶えるといった話でもとても良いものなので積極的に使ってます。
処理の共存というものは、往々にして起こりがちですがそういったものへの解決策にもなります。

ただ、かけ離れた処理を無理やりadapterパターンで期待するものへ機能を書き換えるならば、諦めて新しく処理を実装しましょう。

URLのパースを各言語で行った時の挙動の違い

URLをパースしていて不可思議な現象に立ち会ったので紹介します。

プログラムを書いていて、URLをパースしなければいけない機会というのは結構存在すると思います。
PythonJavaScript両方で同じURLをパースしたら片方でエラーが出て、片方ではうまくいくといった現象が起こりました。

それぞれの言語ではどうなんだろう?と思って4種類程度の言語で確かめてみました。

実験方法

至ってシンプルで、

URL1 http://saruhei1989.hatenablog.com/
URL2 //saruhei1989.hatenablog.com/

の上記2リンクを用意して、それぞれを各言語でパースするだけです。
URL2はプロトコルを省略したものです。一応aタグのsrcに指定したら両方正しく遷移するものを用意しました。
ご想像の通り、僕が遭遇した不可思議パターンのURLです。

結果

言語 URL1 URL2 URLの定義
Python パースされる パースされる RFC3986
JavaScript パースされる エラーが出る RFC3986, RFC3987
PHP パースされる パースされる RFC3986
Ruby パースされる パースされる RFC3986, RFC3987

Pythonの公式ドキュメント : https://docs.python.org/ja/3/library/urllib.parse.html

JavaScriptは公式が見つからなかったので仕様の記載しているもの : https://developer.mozilla.org/ja/docs/Web/API/URL/URL

PHPの公式ドキュメント : https://www.php.net/manual/ja/function.parse-url.php

Rubyの公式ドキュメント : https://docs.ruby-lang.org/ja/latest/class/URI.html

結論はJavaScriptだけエラーがでました。
言語が違うもの、違ってもおかしくないという話ですが、 RubyJavaScriptはURLの定義どころか、実装の際に利用した仕様が全く一緒のようです。

url.spec.whatwg.org

考察

英語が難しすぎて全然理解が進まなかったのですが、 用意したURLは両方RFC3986の仕様を満たすURLではあるようです。
結論どっちが正しいとか全然わからなかったのですが、言えることは

違う言語をまたいで同じだと思われる処理をするのはやめましょう

ということですね。