2020年の振り返り
2020年が終わる前に今年学んだ技術などの振り返りや今後やりたいことなどをまとめてみたいと思います。
フロントエンド
言語
JavaScript・TypeScript(TSがほぼメイン)
JavaScriptはほぼ書いたことがない・TypeScriptは全くの未経験という感じでしたが、良い書籍・サービスのおかげで なんとかキャッチアップできました。
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使ったりしてました。 諸々基本的なサービスの概要レベルは把握しておきたいと思ってDVAやSOAを取得したりもしました。
Iac
- Terraform・ServerlessFramework
基本的にクラウド環境はTerraformを使って構築・管理していました。Terraform自体も経験がなかったので以下の書籍で学習しました。
画面ポチポチしてリソースを作るより、コード化した方が各リソース間の関係も可視化でき、サービスをより理解できるという感じで最高でした。
ServerlessFrameworkは完全に個人レベルでしか使っていませんが、サーバレスなバックエンドを作る際にかなり便利で今後もどんどん使って行きたいという感じでした。 ただ完全にTerraformに慣れてしまったのでServerlessFrameworkのcFnがとても辛いというのが個人的なデメリットでした。
まとめ
今年は2年くらいやっていたカスタマーサポートから開発エンジニアに転職して広く浅くですが技術的に色々経験できて良い一年でした。 来年はもう少し全体的に技術の深堀りをしていきたいのと、自分の得意分野を作る or 最低限得意としい分野を見つけるくらいはやりたいと思っています。 またコンピュータサイエンスの基礎が全くわかっていないのでそのあたりの学習も並行してやっていきたいです。 もう少し業務をそつなくこなせるようになった後に、UoPeopleなどに入学してCSの勉強をしてみたいなという思いもありますが、まずは目の前の事からコツコツ頑張っていきます。
Djangoのフォームのバリデーション
Django公式ドキュメントに書かれてあるフォームのバリデーションについて簡単にまとめてみました。
フォームとフィールドの検証 | Django ドキュメント | Django
公式ドキュメントに書かれている情報をまとめると フォームのバリデーションは以下の順で実行される。
- Formクラスのis_valid()
- Fieldクラスのclean()
- Fieldクラスのto_python()
- Fieldクラスのvalidate()
- Fieldクラスのrun_validators()
- Formクラスのclean_filed名()
- 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()のような書き方をする必要がなく、 インスタンス変数へのアクセスをドット記法で行うことができるためコードがシンプルになる。
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を呼び出す必要がある。
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! >>>