Torekatsuのブログ

日々学んだことを細々と

2020年の振り返り

2020年が終わる前に今年学んだ技術などの振り返りや今後やりたいことなどをまとめてみたいと思います。

フロントエンド

言語
  • JavaScript・TypeScript(TSがほぼメイン)
    JavaScriptはほぼ書いたことがない・TypeScriptは全くの未経験という感じでしたが、良い書籍・サービスのおかげで なんとかキャッチアップできました。

  • www.oreilly.co.jp

  • www.udemy.com

TypeScriptの型はとても柔軟で素晴らしいと思うのですが、コードを書く側の頭も柔軟じゃないと全然使いこなせそうにないなという感じでした。 なんとか良い感じにTypeScriptの型を書けるようになりたいので、年末年始はTypeScript Exercisesをやっています。

フレームワーク
  • Vue.js・React.js(Reactが9割)
  • Next.js(個人開発でのみ)

開発エンジニアへの転職を目指す中でVue.js自体は少しさわっていましたが、現職ではReactがメインでした。 Reactは全くの未経験だったためキャッチアップのためにけっこう勉強した記憶があります。学習に際しては以下の書籍が良い感じでした。

最低限書ける程度なので今後も要学習という感じです。

バックエンド

言語
  • Python・Go
    9割Pythonで、Goは既存のコードリーディングがメインという感じでした。 文法のシンプルさデプロイの容易性に加えて、Dockerやk8sなどインフラのデファクトがGoで書かれていることもあり 今後はGoに力を入れていきたいなという感じでした。
フレームワーク
  • Django・Flask・FastAPI
    割と複雑なWebアプリケーションはDjangoで、簡素なものはFlaskという感じでした。 FastAPIは個人レベルでしか触っていませんが、デフォルトで型付けしたりOpenAPIに対応していて かなり使い勝手が良いのでFlask使うくらいなら断然FastAPIという感じでした。
データベース
  • MySQL・DynamoDB
    基本的にはフレームワークのORMを使っていたので、SQL書いたりパフォーマンスチューニングなどは一切やりませんでした。 これについては業務でも個人レベルでもあまりやれる機会がないのでどうしようかな...という感じでした。

インフラ

クラウド
  • AWS・Azure・GCP
    AWSが8割って感じでした。転職の1年ほど前にSAAは取得していたのでAWSの基本的なサービスについてかなりふんわり理解している程度でした。 転職してからは主にアプリケーションの実行環境としてコンテナサービス(Fargate・ECR)を使ったり、フロントエンドの配信でCloudFront・WAF使ったりしてました。 諸々基本的なサービスの概要レベルは把握しておきたいと思ってDVASOAを取得したりもしました。
Iac
  • Terraform・ServerlessFramework
    基本的にクラウド環境はTerraformを使って構築・管理していました。Terraform自体も経験がなかったので以下の書籍で学習しました。

booth.pm

画面ポチポチしてリソースを作るより、コード化した方が各リソース間の関係も可視化でき、サービスをより理解できるという感じで最高でした。

ServerlessFrameworkは完全に個人レベルでしか使っていませんが、サーバレスなバックエンドを作る際にかなり便利で今後もどんどん使って行きたいという感じでした。 ただ完全にTerraformに慣れてしまったのでServerlessFrameworkのcFnがとても辛いというのが個人的なデメリットでした。

まとめ

今年は2年くらいやっていたカスタマーサポートから開発エンジニアに転職して広く浅くですが技術的に色々経験できて良い一年でした。 来年はもう少し全体的に技術の深堀りをしていきたいのと、自分の得意分野を作る or 最低限得意としい分野を見つけるくらいはやりたいと思っています。 またコンピュータサイエンスの基礎が全くわかっていないのでそのあたりの学習も並行してやっていきたいです。 もう少し業務をそつなくこなせるようになった後に、UoPeopleなどに入学してCSの勉強をしてみたいなという思いもありますが、まずは目の前の事からコツコツ頑張っていきます。

Djangoのフォームのバリデーション

Django公式ドキュメントに書かれてあるフォームのバリデーションについて簡単にまとめてみました。

フォームとフィールドの検証 | Django ドキュメント | Django

公式ドキュメントに書かれている情報をまとめると フォームのバリデーションは以下の順で実行される。

  1. Formクラスのis_valid()
  2. Fieldクラスのclean()
  3. Fieldクラスのto_python()
  4. Fieldクラスのvalidate()
  5. Fieldクラスのrun_validators()
  6. Formクラスのclean_filed名()
  7. Formクラスのclean()

今回は2~5のFieldクラスのバリデーションについて調べてみました。

Fieldクラスのclean()メソッド

まずは、Fieldクラスのclean()メソッドから見ていきます。
Fieldクラスで実行されるバリデーションのエントリポイントがclean()メソッドです。
以下に公式ドキュメントを引用します。

Field サブクラスの clean() メソッドは、to_python()、validate()、run_validators() を正しい順序で実行し、エラーを伝達する役目を負っています。もしこの過程のどこかで ValidationError が投げられた場合、バリデーションは停止し、そのエラーを投げます。このメソッドはクリーニングされたデータを返し、フォームの cleaned_data ディクショナリに格納します。

コードはこんな感じで、
公式ドキュメントの記載通りto_python(), validate(), run_validators()を順に実行しています。

django/fields.py at 98f23a8af0be7e87535426c5c83058e2682bfdf8 · django/django · GitHub

def clean(self, value):
    """
    Validate the given value and return its "cleaned" value as an
    appropriate Python object. Raise ValidationError for any errors.
    """
    value = self.to_python(value)
    self.validate(value)
    self.run_validators(value)
    return value

Fieldクラスのto_python()メソッド

次にto_python()メソッドです。

Field の to_python()メソッドはすべてのバリデーションの最初のステップとなります。これは、値をデータ型に正すことを強制し、それができない場合は ValidationError を投げます。このメソッドはウィジェットからそのままの値を受け取り、変換した値を返します。たとえば、FloatField は Python の float に変換されるか、もしくは ValidationError を投げます。

フィールドの値をPythonの型に変換することでバリデーションを行うみたいです。

django/fields.py at 98f23a8af0be7e87535426c5c83058e2682bfdf8 · django/django · GitHub

def to_python(self, value):
    return value

Fieldクラスでは単に引数をreturnしてますね。
このメソッドはFieldクラスのサブクラスでオーバライドされる事が前提みたいです。
参考例としてCharFieldでオーバライドされたto_python()メソッドを見てみましょう。

django/fields.py at 98f23a8af0be7e87535426c5c83058e2682bfdf8 · django/django · GitHub

def to_python(self, value):
    """Return a string."""
    if value not in self.empty_values:
        value = str(value)
        if self.strip:
            value = value.strip()
    if value in self.empty_values:
        return self.empty_value
    return value

CharFieldのto_python()メソッドでは以下の実装となっています。

  • valueが(None, '', , (), {})でなければ文字列型に変換し、strip()で空白文字を削除しreturn
  • valueが(None, '', , (), {})なら、''をreturn

Fieldクラスのvalidate()メソッド

次にvalidate()メソッドです。

Field の validate()メソッドは、フィールド特有のバリデーションを行います。これはバリデータとしては不適です。正しいデータ型を強制された値を取り、エラーの際は ValidationError を投げます。このメソッドは何も返さず、また値の変更も行わないはずです。バリデータに記述したくないバリデーションロジックを実行するためには、このメソッドをオーバーライドしてください。

うーん、このメソッドはぶっちゃけ何のためにあるのかよくわからないです。。。

django/fields.py at 98f23a8af0be7e87535426c5c83058e2682bfdf8 · django/django · GitHub

def validate(self, value):
    if value in self.empty_values and self.required:
        raise ValidationError(self.error_messages['required'], code='required')

Fieldクラスのvalidate()メソッドでは

  • valueが(None, '', [], (), {})であるか
  • フィールドがrequiredであるか を元にバリデーションを行っています。

このvalidate()メソッドもFiledのサブクラス側でオーバライドされている事があります。

Fieldクラスのrun_validators()メソッド

最後にrun_validators()メソッドです。

Field の run_validators() メソッドはフィールドのバリデータをすべて実行し、すべてのエラーを単一の ValidationError に統合します。このメソッドをオーバーライドする必要はないはずです。

Fieldに設定されたバリデータを全て実行するメソッドで、
基本的にオーバライドはしないメソッドのようです。

django/fields.py at 98f23a8af0be7e87535426c5c83058e2682bfdf8 · django/django · GitHub

def run_validators(self, value):
    if value in self.empty_values:
        return
    errors = []
    for v in self.validators:
        try:
            v(value)
        except ValidationError as e:
            if hasattr(e, 'code') and e.code in self.error_messages:
                e.message = self.error_messages[e.code]
            errors.extend(e.error_list)
    if errors:
        raise ValidationError(errors)

forループで全てのバリデータを実行していますね。

次回はModelFormのバリデーションについて書きたいと思います。

Pythonのpropertyでちょっとハマった

ハマったこと

クラスのインスタンス作成時にpropertyのsetterがうまく呼び出されない。

原因

クラスのinitで直接変数に値を代入していた

Propertyとは

Pythonでは変数のgetter・setterを簡便に設定できるpropertyという組み込み関数が有る。 他言語にあるgetNum()やsetNum()のような書き方をする必要がなく、 インスタンス変数へのアクセスをドット記法で行うことができるためコードがシンプルになる。

sample.py
class Example:
    def __init__(self, num):
        self._num = num

    @property
    def num(self):
        return self._num

    @num.setter
    def num(self, value):
        # 0以下だと例外
        if value <= 0:
            raise ValueError('num must be over 0!')
        self._num = value

上記のクラスを対話モードで使ってみる。

対話モード
>>> from sample import Example
>>> example = Example(10)
>>> example.num
10
>>> example.num = 1
>>> example.num
1
>>> example.num = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "sample.py", line 12, in num
    raise ValueError("num must be over 0!")
ValueError: num must be over 0!
>>>

ただし、上記のコードではinit内で直接変数に値を代入しているため インスタンス作成時にはsetterが動作しない。

対話モード
>>> from sample import Example
>>> example = Example(0)
>>> example.num
>>> 0

そのためインスタンス作成時にsetterを動作させるには init内でsetterを呼び出す必要がある。

sample.py
class Example:
    def __init__(self, num):
        # self._num = num

        # setterを呼び出す
        self.num = num

    @property
    def num(self):
        return self._num

    @num.setter
    def num(self, value):
        if value <= 0:
            raise ValueError('num must be over 0!')
        self._num = value

同様に対話モードで使ってみる。 先ほどの例とは違い、インスタンス作成時にsetterが動作し 例外が投げられている。

対話モード
>>> from sample import Example
>>> example = Example(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "sample.py", line 3, in __init__
    self.num = num
  File "sample.py", line 12, in num
    raise ValueError("num must be over 0!")
ValueError: num must be over 0!
>>>