[Python] 新卒エンジニアが人手不足の案件にぶち込まれて学んだこと・思ったこと

(注)Python初心者が書いている記事です。記述内容はなるべく裏を取るようにしていますが、誤りを含む可能性があります。ご了承ください。
# はじめに
誤解を招きそうなタイトルですが、この記事は「炎上しそうな案件にいきなりぶっ込まれた新卒エンジニアが色々あがいて圧倒的成長をしたぜ！！！」的な話**ではない**です。

# じゃあ何の話なのよ
もっぱらPythonの文法とかの話です。
プロジェクトの進め方とか新卒エンジニアが炎上してる仕事に取り組む際の姿勢とかその手の話はゼロです。(そもそも炎上案件ではない)
「じゃあ公式ドキュメントなり解説書なり読んだ方がええやん！」というツッコミが来そうですが、それでもこの記事を書いている理由は2つあります。

- 自分用の備忘録として
    - 自分が学んだ内容を後から自分で見返すため
    - 学んだ時に個人的に抱いた感想なども記録しておきたかった
- 初心者はこういう所に躓いたり、目新しさを感じたりするんだよ、という共有
    - 「初心者の視点を提供する」という意味でも何かの役に立ったら良いな、と思っています。
    - IT系の経験が乏しい新人さんに指導する立場の人などは参考になるかも？

# 改めて諸々の状況

## 筆者の状況
Pythonの話をすると言った側から身の上話ですが、筆者が今回の案件にアサインされた際の状況をちょっとだけ説明します。
昨年の12月くらいに、普段自分が関わっている案件とは別のプロジェクトに突如ぶち込まれました。人手が足りないということと、自分が(ほんの少しだけ)Pythonを触っていたということから、Pythonを使う本案件にアサインされました(プロジェクトの詳細はヒミツです)

### 筆者のプログラミング歴
- 筆者は理系ですが、情報系の学科の出身ではないです。
    - 授業でほんの僅かにプログラミングに触れたことはありましたが、そこで学んで身についた事はほとんどありませんでした。
    - 大学院に進学後、研究や趣味で必要になりちょっとしたコードをちょっとだけ書きました
- 入社前にpaizaのPythonの初心者向け講座を、入社してからは(メイン業務で使うため)C++のUdemy講座を受講しました。どちらも基礎文法的な内容です。

## プロジェクトの状況
自分のアサイン期間は、23年12月〜24年1月, 2月ごろと非常に短いです。
この短い期間にプロジェクトのキャッチアップをして、実装をするというややタイトなスケジュールでした。ですが、バックアップとしてスーパーつよつよテックリードの先輩がいらっしゃったので、(自分目線では)そこまで大変ではありませんでした。安心できる環境で(締切には追われつつも)色々な実装をすることで、Pythonについてちょっとだけ知識を深めることができました。
この「安心して『背中を任せられる』先輩のフォローの元で締切に追われながらコーディングをする」という状況が適度な緊張感をもたらしてくれており、学習する上で理想的な環境でした。
また、実装にあたって求められる知識レベルも「Python初心者がちょっと背伸びしたら届きそうな所にある知識」くらいの感じだったので、実装難易度もちょうど良く、かなり成長できた実感があります。

# 学んだこと
では本題。

## 複数のファイルをまたぐということ
まず学んだこととして、詳細な文法事項とかより先に話しておきたいのがこれです。
自分は入社前はPythonでは単体で完結するようなスクリプトファイルしか書いて来ませんでした。入社後は流石に複数のファイル群から構成されるソフトウェアを触る機会はありましたが、多くのケースではごく一部のファイルを参照するだけで済んでいました。
ですが、今回のプロジェクトでは「入力インターフェースを実装する」というタスクが課せられました。そのため、「互いに参照したりされたりする複数のファイル群を設計し作成する」ことが前提となっています。
設計の方針や雛形等は先輩に決めてもらっており「レールが敷かれた」ような状態でしたが、それでも自分でインターフェースやらクラスやらを定義して継承して……みたいなことをやった事がなく、それなりの規模で複数のファイル群を相手するのは初めてでした。最終的に自分が直接書いたファイルの数はそこまで多くなりませんでしたが、既存実装を理解するためにあちこちのファイルを行き来するのは慣れるまでは大変でした。

### インターフェース・抽象メソッド
色々ぼかして書きますが、ある形式のデータAを入力して処理する「既存実装a」が、自分がプロジェクトに参加する前からありました。
今回の自分のタスクは、
- データBの入力にも対応できるように「実装b」を作る
- 今後データC, Dと対応させたいデータ形式が増えていくことが想定されるので、既存実装の上位レイヤーに相当するインターフェースを作成する

といった感じでした。
こういったインターフェースを実装するのは初めてで何が最善の方法か、と言う以前にどうすれば実装できるのかそもそも知りませんでした。
先輩からは抽象メソッドを使って実装するよう言われたのでひとまずそれに則って実装してみました。
抽象メソッドの記述自体はとても簡単で、

```python
import abc

@abc.abstractmethod
def some_function(self):
    pass
```

これで一丁上がりです。
実際の業務では、こういった抽象メソッドの羅列を記述しただけのインターフェース用のファイル(`interface.py`みたいなやつ)を作成しました。
メソッド内の実装が`pass`だけなのは具体例だから実際の処理を書いていないのではなく、**本当に実装が空っぽ**なのです。あくまで、継承して使うことを前提とするメソッドです。
一応同様の概念(純粋仮想関数)はUdemyのC++講座で触れていたものの、実際の業務で書くのもPythonで記述するのも初めてでした。なので、わざわざ空っぽのメソッドを記述するのはなんだか奇妙な感じがしました。(空っぽの箱を作っただけなのにそれをさも「仕事しましたよ！」と言わんばかりのノリでコミット&プッシュしたりチケットに報告したりするのも変な感じでした)
こうして作った抽象メソッドは、実装aや実装bに継承してきて、それぞれに合った処理を記述していきます。
このようにインターフェースを作って抽象化するような事は、個人の開発ではよほど複雑にならない限りあまり意味がないように感じるのですが、複数人で開発する際は以下のようなメリットがあるように感じました。(「抽象化」について聞き齧ったことの受け売りになってるような気もするが……)
- 共同開発者が新しく実装cや実装dを生やす際に、一々名称に悩まずに済む
- 新しくプロジェクトに入ってきた人が、大まかにどう言う処理があるのか見通しやすい
    - 本の目次のような感覚かな？

### モジュールのインポート
上で作成したインターフェースを、ファイルを跨いで別のファイルでインポートすると言うのも初心者の自分にとっては新鮮でした。
といっても簡単で、普通にモジュールをインポートする時のようにコードの頭にimportと書くだけです。
ただし、標準ライブラリ等をインポートする時と違って、ディレクトリ構造等に注意しなきゃいけないのがポイントです。例えば同じディレクトリにある`interface.py`をインポートしたいのであれば以下のように書きます。

```python
from . import interface
```

普段からターミナルで`cd`コマンドを叩いていれば、.がカレントディレクトリを表すのも自然と受け入れられます。ただし、モジュール名には拡張子の`.py`がつかないのでそこだけ注意。
パスの指定さえ正しければ、
```python
from .. import interface
```
のように1つの上のディレクトリにあるモジュールを指定したり、あるいは絶対パスでパス情報を渡すなんて事も出来るそうです(試してないけど)。

このようにインターフェース部分だけをあえて個別のファイルとして外に出した上でインポートすると言うのは、作業前は一見無駄なように思っていました。しかし、機能を次々実装していってコードが長くなるにつれて、一部の機能だけを分離するのは見やすくて合理的だと感じるようになりました。コーディングスタイルとかの本で聞き齧ったような話がようやく納得できました。

## データクラス、型アノテーション

今回の業務で触れた中でもう1つ自分にとって新鮮だったのは、データクラスや型アノテーションなど型周りの文法でした。
業務や勉強でちょいちょいC++に触れてきて、静的型付け言語の文法と型を定義するメリットはふんわりと理解していたつもりでしたが、Pythonのような動的型付け言語を使いながら静的型付け言語のような「お作法」に則って記述するのはほぼ初めての経験でした。
データクラスについてはまだ勉強中ですが、今のところ「変数を宣言する事に特化したクラス」という理解をしています。少なくとも自分が触れたコードでは「いくつかの変数のセットをテンプレートとして用意しておく」といった使われ方をしていたように思います。
以下、記述例です

```python
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int
```

まずはデータクラスのモジュールをインポートしておき、`@dataclass`と書いてから普通のクラスと同じように宣言するだけです。(ちなみに、抽象メソッドの時もありましたが@から始まる修飾を「デコレータ」と呼ぶそうです。知らなかった……)
その後クラス内で変数(データクラス内の変数はフィールドと呼ぶらしいです)を宣言するのですが、この際に変数名だけでなく型の情報も書く必要があると言うのがポイントです。
また、データクラスは変数の宣言に特化したクラスと先に述べましたが、一応普通のクラス同様にメソッドを定義することもできるらしいです。記述例としては以下のような感じ。

```python
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

    def distance_to_origin(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5
```

この時、メソッドでも `-> float`のように返り値の型も定義してあげてるのがポイントですね。
ちなみに、この `-> float`や`: int`のように型の情報を書く文法を「型アノテーション」や「型ヒント」と呼ぶらしいです。
これらの文法を扱う上で最も注意しなければならないのは、型アノテーションは**あくまで注釈に過ぎず、コードの実行時に型を強制する力はない**と言うことです。(まあPythonが動的型付け言語なのでそりゃそうかって感じですが)例えば以下のようなコードを実行してみると一目瞭然でしょう。

```python
def add(x: int, y: int) -> int:
    return x + y

# 型アノテーションに従った正しい使用
result1 = add(1, 2)
print(result1)  # 3

# 型アノテーションに反して異なる型の引数を渡す
result2 = add("1", "2")
print(result2)  # 12
```

このコードは特にエラーとかも吐かずに実行され、result2の方では型アノテーションで定義したのとは違いstr型の引数が渡されてしまいます。

### 動的型付けと静的型付けについて改めて思ったこと
ここで少し余談というか、ちょっとしたお気持ちを書きます。
以前趣味レベルでPythonに触っていた(個人で簡単なスクリプトを書く程度)頃は、CやJavaと違って一々型を定義しなくて楽だと思っていました。その一方で、上記の点を指して「これだからPythonは良くない」みたいな物言いをしている人の声もちらほら耳にしていました。つまり、自分がPythonのメリットだと感じている部分をデメリットであると主張する人がいたのです。
以前自分はこういった声を聞いて不思議に思っていたものですが、今回業務でPythonに触れて、その主張が少しは分かるようになりました(Pythonアンチになったとかではないけど)。業務等で集団開発をしていると、ほとんど知らないコードばかりな中に自分が新しくコードを書くという場面が出てくると思います。そのような場面では、自分が実装しようとしている関数に渡ってくる変数の型が分からない、みたいな状況が往々にしてあります。そういった際に型アノテーションがされていると、しかるべき場所を参照すれば渡ってくる変数の型を知る事ができます。また自分が書くコードについても、膨大な数の変数を扱う際にデータクラス等できちんと定義しておけば変数名や型情報がごっちゃになることもありませんし、一々コメント等に書かなくても他の人に伝わるだろうと安心できます。
こういった型定義のメリットに関する話は一般論としては聞き齧っていたものの、あまり実感が湧いていませんでした。それが今回こうして業務としてコードに触れ実装をすることで、初めて実のある理解ができたと思います。

# 終わりに
今回の案件を通じて学んだのは、抽象メソッドにせよデータクラスにせよ、「複数人で開発する際に便利な機能・文法」だったように思います。今まで公私ともに集団開発の経験がほぼ無かった(仕事でもどちらかというと調査が多かった)ので、こうした文法に触れる機会はあまりありませんでした。こういった文法は、型アノテーションなどは特に顕著ですが、**機能面ではほとんど意味がなく、ただ人がコードを扱う上で便利なだけの記法**と言うのが興味深かったです。極論誰もが瞬時に型情報を読み解ければこういった文法はおそらく必要ないのですが、実際はそうではなかったと言うことなのでしょう。こういった人間側のスペックの低さを考慮して言語が設計されていると言うのは何とも興味深いと改めて思いました。
脱線しますが、IT界隈では上記の話以外にも「人間は完璧じゃない」と言うことを前提としてる仕組みや風習があるように感じています。開発が終わった後にテストと言う工程があるのもそうですし、文法の誤りがある際に(通常は)エラー等で教えてくれるというのもそうです。こういった風習は、人間はミスを犯す生き物であるという事を織り込んだ合理的な判断の積み重ねだと推測します。
ところで、別業界から来た自分目線だと、IT業界の人は比較的人のミスや失敗に対して寛容な気がしています。こういった性質は、もしかしたら上記で書いた風習から「人はミスをする」ということを常日頃から刷り込まれているためかもしれないですね。(自分が入社した会社がたまたま雰囲気良かっただけと言う可能性もありますが……)

最後の方はなんだかポエムみたいになってしまいましたが、いかがでしたでしょうか。基本は自分の知識や思考の整理に書いた記事ですが、初学者の勉強補助や初学者の視点を知りたい指導者の方の助けになりましたら幸いです。