2022年の振り返り

はじめに

気がつけば仕事が納まっちゃって年の瀬です。色々とあった年だったので1年間を振り返ってまとめておこうと思います。イベントはたくさんあった割にさほどストレスもなく、割とのんびりした一年だったなというのが感想です。ボケーっとしてると何をやったのかも忘れてたりするので時系列で振り返っていこうと思います。

大学院を卒業

3月に2年間通っていた東京大学大学院を卒業しました。所属は学際情報学府の総合分析情報学コースというところで、英語だとApplied Computer Scienceっていう名前のなんかカッコいいところです。学部は横浜国立大学の機械工学科(EP)だったので専門と大学がガラッと変わってました。大学院は学びに来るところじゃねぇ、研究するところだって怒られそうな気もしますが、自分は大学院でComputer Scienceを学びながらついでに色々手を動かす経験が欲しくて入学してます。結果として大正解だったなという2年間で、学びが非常に多くて良かったです。 東大発のAIベンチャーで長期インターンを1年半ほどさせてもらったり、同期と勉強会や論文輪講をホストしたり、研究テーマを考えるためにサーベイしたり、etc...と割と自由に色々やってました。うまくいくことよりもうまくいかないことの方が多いくらいで失敗もたくさんしました。特に自分でテーマ設定からした研究は、テーマ的に評価が難しく、あまり綺麗に研究が出来なかったです。1年から2年程度でまとめることができて、現実的に解けそうな課題であり、かつ難しくて誰もまだやっていないようなテーマを見つけて設定するというのはそれだけでなかなかに骨が折れるタスクで、研究の半分はテーマ設定だとも言われている理由がよくわかりました。ド派手な成果こそ出せてませんが、自分の血肉になる経験が得られた2年間だと思っています。

就職、配属

入社〜研修 / 4月-6月

大学院を卒業し、4月からはYahoo! JapanというところでSoftware Engineerとして働いています。最初の3ヶ月くらいは座学ベースの研修で内容的にも正直物足りないなと思いながら過ごしてましたが、7月からのOJTはかなり実践的で楽しかったです。OJTは配属先によってデザインが異なるので当たりの部署だったみたいで良かったです。

OJT / 7月-9月

OJTの内容としては影響範囲の小さい開発案件を新卒だけでハンドリングするというもので、ここでも色々失敗しました。引き算で要件を考えること(要件をできるだけminimumにしてサボる)、コンフリクトしないようにタスクを並列化すること、スケジューリング、先輩のリソースの使い方、同期とのコミュニケーション、遅延しそうな時のエスカレーション報告、リスク管理等々のマネジメント的なところがズタボロでひどかったです。分解されたタスクをハンドリングするだけなら自分は期待値通りのdelivery速度でoutputが出せる(た)のですが、配属されて「初めまして」したばかりでチームビルディングもろくにできてない、各々の力量もわからないチームでうまくタスクを分解してアサインを決めて、ってするのはかなり難しかったです。どうすれば良かったという反省を書いちゃうと具体的な話に踏み込まざるを得ないので書けませんが、まあいっぱい失敗しました。

結果として当初の要求(1ヶ月で設計からdeliveryまで)から大きく外れて2ヶ月間かけてサービスを提供するに至りました。影響範囲の小さな開発を実業務から切り出して、諸々失敗するところまで想定してOJT案件を設計してくださって大変ありがたかったです。来年以降は後輩に同じような体験を提供できるように現在の業務からいい塩梅の規模感の案件を切り出せるようになってたいです。

本配属 / 10月-現在

10月からは本配属されてOJTでお世話になっていた部署とは別の、新規事業の開発を行っている部署にアサインされました。事前に希望していた部署だったので良かったです。初めの1ヶ月半くらいは1週間程度、ないしは数日で終わるタスクを貰ってプロジェクトの全体像とかを把握しながらタスクをこなす、みたいな感じで進んでいきました。弊社にしては珍しくベンチャー気質のあるプロジェクトなので色々整ってない部分も多く、受け身な姿勢でいると割としんどいというか、新卒だからと言って面倒を見てもらえる感じではないので積極的に色々自分からやる必要があって、大企業でもこういうプロジェクトあるのかとびっくりしました。11月後半くらいで中・大規模な開発案件を任せてもらえたので概要設計やステークホルダーとの調整等を済ませて絶賛開発中です。弊社のような大企業で、チームにjoinしてから1、2ヶ月で数ヶ月スパンでスケジューリングが必要な案件を持たせて貰えてるのは恵まれてる気はしてます。結構楽しいですが、もっと難しい案件任せて良さそうだねと上司から評価してもらってる通り、まだ余裕を持ってハンドリングできてる感じがするので今後はより難易度の高い案件を任せてもらえそうで大変満足してます。OJTと違って失敗するとちょっとヤバいので気合い入れて取り組もうと思います。

会社について

ここで働く選択でいいんだっけ?選択としてどうだったんだろうってのはそれだけでかなりの量の言語化を要するので別でやろうと思います。新卒で弊社を志望している人もいたり居なかったりすると思うので参考になる情報源としてなんか書けたらいいですかね。

総括

できるかできないかわからないことをやる環境に今ちゃんと身を置けていて、うまくいくこともあれば、すっ転んじゃうこともある、そしてそれが当該案件の中だけで閉じずに今後に活かせる経験であるのであればこんなにいいことはないと思っています。ほどほどに楽しく過ごせているので来年以降もこの調子でのらりくらりと楽しみながら仕事やりつつ、プライベートでは今いる友人を大切にしながら過ごせたらなと思います。以上!!!!

Spring BootにSwaggerを導入する

0. 初めに

仕事でSwaggerを利用することになりました。Swaggerの利用方法・アプローチは複数ある*1ようですが、今回はJava(Spring)のコードからDocumentを生成するアプローチを取ります。

Swaggerそのものの説明は殆ど書いてないです。

1. 各種バージョン情報について

Java

# Java
$ java --version
java 17.0.3.1 2022-04-22 LTS
Java(TM) SE Runtime Environment (build 17.0.3.1+2-LTS-6)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.3.1+2-LTS-6, mixed mode, sharing)

Spring、SpringFox(SpringのSwagger用ライブラリ)

Springは2.6.7

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/> <!-- lookup parent from repository -->
</parent>

~~ 中略 ~~

<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
</dependency>

その他

# mac
❯ sysctl -a machdep.cpu
machdep.cpu.cores_per_package: 10
machdep.cpu.core_count: 10
machdep.cpu.logical_per_package: 10
machdep.cpu.thread_count: 10
machdep.cpu.brand_string: Apple M1 Max

# IntelliJ IDEA Community Edition
version: 2021.3.3

2. SpringFoxとは

SwaggerをSpringで利用する際に必要となるライブラリです。2022年7月現在、最新のバージョンは3.0.0です。

リリースの情報を読んでる限り、2系との特に大きな差分としてはWebfluxが使えることのようです。spring boot 2.6系との噛み合わせがまだ悪いみたいな話もあるようですが、以下のように@EnableWebMVCアノテーションをエントリーポイントに追加するだけでとりあえず動きました。

@EnableWebMvc
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

以下、原文をそのまんま引用しておきます。(参考

  • Highlights of this release include
    • Spring 5, Webflux support (only request mapping support, functional endpoints aren't supported yet)
    • Spring Integration support (feedback is much appreciated)
    • Spring Boot support springfox-boot-starter dependency (Zero Config, Autoconfiguration support)
    • Documented Configuration Properties with autocomplete
    • Better specification compatibility with 2.0
    • Support for OpenApi 3.0.3
    • Zero Dependency (almost, the only libraries needed are spring-plugin, pswagger-core](https://github.com/swagger-api/swagger-core)
    • Existing swagger2 annotations will continue to work and enrich open api 3.0 specification

3. 設定ファイルを記述する

適当な階層にconfigファイルを記述します。自分はentry pointと同じ階層に記述しました。

package com. example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                // タイトル
                .title("文書分類API")
                // 詳細な説明
                .description("文書を登録すると、文書の分類を行なってくれるAPIです")
                // バージョン
                .version("1.0.0")
                // 連絡先
                .contact(new Contact("takerumimata", "https://mimatasanmata.hatenablog.com", "takerumimata@example.com"))
                // ライセンス名
                .license("Apache 2.0")
                // ライセンスURL
                .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
                // 利用規約のURL
                .termsOfServiceUrl("http://example.com/termsofuse")
                .build();
    }
}

DocketというBeanを登録しています。Docketという単語を初めて目にしたので意味を調べてみたところ、明細、などの意味があるようです。(参考)かなり硬い言葉のよう?

Docketと別に、ApiInfoという型を返すクラスを用意しています。こちらはSwagger-UIでAPIのDocumentを表示する際に、そのAPIのタイトルや概略を表示する際に利用する情報を記述することができるようです。

3.1 API Documentを確認する

mvnコマンドやIntellijなどを利用してSpringアプリケーションを走らせてください。起動したらhttp://localhost:8080/swagger-ui/ へアクセス。以下のように、Controllerを勝手に解析してDocumentを吐き出してくれます。

swagger-ui

v2/api-docsへアクセスすると、Swagger Spec(Json)を取得することもできます。

3.2 表示するAPIを絞る

表示するAPIを限定する手法について説明します。ここでは例としてbasic-error-controllerを表示対象から外すことにしました。basic-error-controllerはSpringがデフォルトで用意しているエラーコントローラですが、Documentとして表示する必要はないので見えないようにします。

SwaggerConfigを以下のように編集。

@Bean
public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
      .select()
    // パッケージ指定によって表示するAPIを絞り込む場合。例えばControllerだけ表示したい場合は以下
    .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
    .paths(PathSelectors.any())
    // パス指定で絞り込む場合
    //.paths(PathSelectors.ant("/todos/**"))
    .build()
    .apiInfo(apiInfo());
}

コメントに書いてある通りですが、package、またはpathで絞り込みが行えます。

表示するAPIを制限する

4. アノテーションを記述する:Documentのカスタマイズ

この時点で既にDocumentの大枠は自動生成されています。次に、生成されたDocumentをカスタマイズする方法について述べます。

細かい設定はやり出すと止まらないので、少しだけ例を挙げます。まずは下記コードをご覧ください。Todoアプリっぽい感じで、todoを追加するPost系のAPIと、一覧を取得するGet系のAPIです。型とかは適当なので一覧といいつつString型で値を返す感じになってますが、細けぇことは気にしないでください。

       // SampleController.java
        @GetMapping(path = "/todos", produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "TODOの一覧を取得します", notes = "idを指定して絞り込みもできます")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "正常系"),
            @ApiResponse(code = 404, message = "not found")
    })
    public String todoList() {
        return "Todos...";
    }

    @PostMapping(path = "/todo", produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "TODOを追加", notes = "hoge")
    public void todoAdd(@RequestBody String todo) {
        // do something
    }

@ApiResponses@ApiResponseなどをメソッドにアノテーションとして付与し、parameterを渡していくと、自動生成されるものではなく、自分で設定したパラメータでDocumentを生成してくれます。

めんどくさかったので記述してませんが、Example Valueなども@Contentで設定できるようです。

アノテーションの一覧はGithub Wikiにまとまっています。

Swagger 2.X Annotations · swagger-api/swagger-core Wiki

5. まとめ

SpringFoxを利用してSpring Bootで作成したAPIのDocumentを生成する方法についてざっとみてみました。Swagger Specを記述してからSpringのコードをGenerateするアプローチを取ることも今後あるかと思うので、どこかで勉強しておきたいですね。

6. Reference

GitHub - springfox/springfox: Automated JSON API documentation for API's built with Spring

Spring Boot + SpringFoxでSwaggerを利用してソースからAPIキュメントを生成する - Qiita

Spring Bootで作成したWEB APIにSwaggerを導入して簡単ドキュメント管理

*1:SwaggerにはTop Downなアプローチ / Bottom Upなアプローチがあります。

Python:TypeError: hoge() takes 1 positional argument but 2 were given

Context & Probrem

雑多に書いていた関数群をリファクタリングしてクラスとしてまとめていた時にタイトルのようなエラーが出た。

TypeError: hoge() takes 1 positional argument but 2 were given

あれ、おかしいな、と。エラーの内容としてはhogeメソッドが受け取る予定の引数は1つしかないが、2つ渡されているぞ、ということ。コードを見返しても2つも引数指定なんてしていないしどういうこっちゃとなった。

Solution

selfをメソッド引数に生やし忘れてた。言語仕様としてselfを書かないとこうなる。

# 実行時にエラーを吐くコード
class fuga:
   def hoge(xxx):
        pass # なんかしらの処理
# 実行できちゃうコード
class fuga:
   def hoge(self, xxx):
        pass # なんかしらの処理

fuga.hoge()という要領でメソッドの呼び出しを行うと、hogeの第一引数にはfugaが入る仕様になっている。仕様を知らないとなぜエラーが出ているのか表面的にはわからず混乱する事になる。Pythonのこの仕様はプログラマからも評判があまり良くないようで、変更するような提案がなされているらしいが、Pythonの生みの親Guidoによってなぜselfを明記する仕様が重要なことなのかを説明するブログもある。あまり単純な話ではないようで自分もまだ完全に咀嚼しきれていないので言及は避ける。

Reference

大変勉強になった記事

足を踏まれている者の痛みは足を踏まれているものにしかわからない

はじめに

数年前のことですが、親族や友人に辛かった出来事、今辛い思いをしているという話をすると、「そんなものは社会に出ればよくある話だ」「俺(私)だってそのくらいの辛いことはある」「本当に辛かったらうつ病になってて家から出られないから笑」「まぁメンタル鍛えられてるって思えばいいんじゃない」と、自分の気持ちに寄り添うことなく流したり、あるいは辛くて苦しいという僕の気持ちを否定されたように感じる場面があったりしました。今別に辛いかと言われるとゆるゆると、ラフに生きている身なので特に辛いとかはないですが、当時のことを思い出してモヤモヤすることがあったので、言語化して整理しておこう、と思った次第です。

初めての研究室で

今は情報系の大学院に通っているのですが、学部時代は機械系の専攻でした。学部3年生の時くらいに自分は機械じゃなくてソフトウェアの方が向いてるなと感じたので、大学院からは情報系に移ろうと決心しました。とはいえ大学院というのは基本的には研究をするための機関なので、研究をする上で最低限の知識はないと当然ながらダメです。情報系の学部をすっ飛ばして大学院に入学をすることになるので、0から情報工学を学ばなければそもそも大学院の試験をパスできませんし、パスした後も研究をするには多くの知識が要求されます。専門の勉強はほどほどにして足りない知識を補うために毎日必死に勉強したのを覚えています。大学の図書館が閉まった後、知り合いのつてでコッソリ大学のとあるスペースで日が変わるくらいまで勉強してた日々は懐かしいです。この姿勢はB4で研究室配属された後も続きます。

当時の僕の指導教員は専門に身を入れてない僕の態度を快く思わなかったようで、かなり冷たい対応をとられていました。

当時取り組んでいた研究の相談をすると

「しらねーよ、俺の研究じゃねーし」

と言われたり、自分の仮説の筋が悪いと、舌打ちしたり、頭を叩かれたりしながら

「まだそんなこと言ってんのかよ...いい加減学べよお前。」

「お前よりひどいやつ見たことねーよ。何も分かってない。これ以上言うと人格否定になるから言わねーけど、さぁ」

「あ?何がいいてぇんだよ、言ってることが支離滅裂。いい加減にしろよ。」

「半年卒業ずらすか。このままじゃ間に合わないし。」

などの発言を相当高圧的な態度でされました。この年にもなって恥ずかしながら泣きかけました。まじで怖かった。

研究室によっては通ってさえいれば教授がある程度やるべきことや実験の方針などを一緒に相談しながら決めてくれて、スイスイ研究が進んでいくものですが、僕の所属していた研究室は、研究の方針は自分で考えさせる方針だったため(研究の方針を教授が決めると与えられたタスクを消化するだけで教育にならないため)大学に通えばどうにかなるのかというと、そんなことはありませんでした。同期や先輩方に相談に乗ってもらったりしながら研究を進めようとするのですが、なかなか思うように行かず、じゃあ指導教員に相談できるのかと言うと、前述したような対応をとられるので恐怖でアポは取れず、時間がただただ過ぎ去っていきました。

進捗を出さずに相談に行くとキレられるし、かといって相談せずに進捗が出るのかというと、研究はそれほど甘くなく、自分はそれができるほど賢くなく、時間は過ぎて刻限が近づく。他の研究室の友達が卒論執筆に取り掛かろうとするときに、自分はほとんど成果が出せないまま、行く先のわからない暗闇の中を歩き回っているような感覚に陥っていて不安と焦燥と絶望と恐怖で頭がおかしくなりそうでした。というより完全にパニック状態か鬱だったと思います。食が細くなってカロリーメイト一本とかで食事を済ませるようになったり、夜眠れない日々が数ヶ月続いてました。

精神的に相当追い詰められていたため、あまりにも辛くて休学を考えたり、希死念慮にかられて楽な自殺方法を調べたり、腹いせに研究室の資材を全て破壊してやろうかとか色々考えてました。 しかし大学院に合格していたため何がなんでもストレートで卒業しないと、せっかく頑張って舵を切り直そうとしている自分の人生が壊れてしまうため休学等はできず、辛い日々を耐えるしかなかったです。またうつ病診断されると就活に響くんじゃないか、うつ病だったとしても就活前に診断をもらうのはよくないかもしれない、とか思ってたりもして、余計病院には行けなかったです。

卒論発表の二週間前とかになってもまとまった成果が出てなかったので、自宅は風呂に入る時のみ利用して、それ以外の時間は研究室に寝泊まりし、シミュレーションをぶん回しながらロジックがボロボロのプレゼン資料と卒論を作ってました。シミュレーション、卒論執筆、プレゼン資料作成を同時並行的にこなしがら、詰まったところはボコボコにされること覚悟で指導教員に相談、泣きながら形だけの卒論を仕上げて、終わった後は気が抜けて3日くらい家でぼーっとしてました。精神的にも体力的にもめちゃんこしんどかったです。

足を踏まれている者の痛みは足を踏まれているものにしかわからない

大体はこんな感じの話をしながら「いやぁ最近まじで辛くてさぁ」って友人や家族に話してました。僕はぱっと見はメンタルが強いように見えるらしく、後から聞くとまさか希死念慮が沸くほど辛いと思ってなかった、とか言われたりもしましたが、当時は冒頭述べたような反応をされることが多く、SOSのメッセージを誰も受け取ってくれね〜〜無理〜〜〜〜ってなってました。人によっては、ある程度筋立ててパワハラで精神が参ってた時期があると話しても、否定してきて辛かったです。まぁ多分そいつは僕のことを友達だとは思ってなかったからそういう反応だったのかも知んないです。友達は選んだ方がいいかも知んないっすね。

さて、長々と前置きをしたところで本題に入ると、重要なのは当時の僕の経験を、あなたが辛いものだと捉えたか、それとも別にそうでもないと捉えたかは、僕にとってはどうでもいいということです。 日常会話の中で辛い経験を友人や家族に共有することは多くの人がすることだと思いますが、その際に「社会にはその手の辛いことがたくさんある」「僕(私)だってそういうことがあった。別にお前だけじゃない」と社会一般というどこからどう取り出したのかわからないモノサシと、自分の主観というモノサシで、他者の辛さを測ってそんなものは矮小なものだと吐き捨てる。そういう攻撃的な反応をする人がいます。この時、足を踏まれて痛みを感じているのは僕なのに、足を踏まれている僕を観測した第三者が僕の痛みを推し量り、評価しているわけですが、痛みの度合いは僕にしかわからないものです。あなたが痛みを評価するのは誤りです。

じゃあどう接するのが正解なのかと言われると難しいですが、辛くて苦しくて死にそうな思いをしている(と言ってる)友人に対してさらに追い込むような言動をとるのは間違ってるんじゃあないかなぁと思うわけです。繰り返しになりますが。気持ちを汲んで、辛いんだね、と話をただ聞くだけで、誰かに承認されるだけで気持ちが軽くなることもあるかもしれないですし、場合によっては一緒に悩んで解決策を考えることなんかもできると思います。僕はその方が建設的だと思うし、優しい対応なんじゃないかなぁと思うっす。

終わり

秋になってポエムを吐きたくなったので僕が辛かった時の記憶を吐いて、ついでにポエム書いてスッキリしようって思ってポエムを書きました。 実はモヤモヤしてることが完全には解消されてなくて、「耳障りのいい言葉を吐くだけが友情ではない」とも思ってるので、ときには厳しめの意見を友人にぶつけることも必要だよなぁとも思ったりしていて、バランス感覚が悩ましいです。コミュニケーションはナマモノなので脳みそ使って適切な対応を考えないとダメっすね。

【書評】A Philosophy of Software Design 1章-2章

はじめに

本記事はソフトウェアの Complexity (複雑さ) について扱っているA Philosophy of Software Designを読んだ際の個人的な備忘録です。170Pほどの洋書で読みやすいので、読みながら学びになったことをまとめていきます。読みやすいとは言え英語なので和書の5倍くらいの時間がかかりますが、頑張って読んでいきます。

www.amazon.co.jp

1章:イントロダクション

ソフトウェアを開発していくとソフトウェアは次第に複雑になっていき、ソースコードの修正や機能追加が難しくなっていく。結果として機能追加にかかる時間が増大したりバグを引き起こすこととなるが、これはソフトウェアの複雑さに起因すると著者は語る。なんらかのツールによって複雑さを抑えることも可能ではあるが、限界はある。従ってソフトウェアデザインにおいて、ソフトウェアの複雑さを最小にすることで、ソフトウェアをシンプルに保つことが重要である。

よりプログラムをシンプルにするにはどうすればいいか、一般的には以下の二つの手法がある。

  • コードをよりシンプルにすることで複雑さを減らすこと
    • 特殊ケースを消す
    • 統一性のある識別子を利用すること
  • 複雑さをカプセル化し、意識すべき複雑さを減らすこと=moduler design

....とだいたい上記のような内容でした。目から鱗の大発見!!とかはまあなくて、ソフトウェアの複雑性を抑えるにはどうすればいいのだろうかを考える本だよってこと、そしてそれがいかに重要であるかを述べています。introなんで、まあ「ソフトウェアの複雑さを最小化すること」が本書の目的だってわかればまあそれでよしって感じで流し読みしました。次!

2章:複雑さの性質

本書の目的は複雑さを最小化するための原則を知ることにあるわけですが、そもそも複雑さとはなんだろう?2章では高いレイヤーから議論を行っており、より低いレイヤーの議論は後続の章で語られている。まずは敵を知ることから。

Complexity defined | 複雑さの定義

自分が直感的に感じていたことを数式モデルで表現しており、割と良いな!って思った定義が以下です。


C = \sum_{p}c_pt_p \\
C...Complexity of System(ソフトウェアの複雑さ)\\

c_p...part(p) weigit(そのpartの複雑さ)\\

t...developerがそこに触れる頻度や時間 \\

ある関数がとても複雑な処理をしていて、中身がもはやブラックボックスになっていたとします。これはあなたの開発しているソフトウェアの中で複雑さに寄与しているでしょうか? 答えはそこに触れる時間による、です。

普段我々が利用しているライブラリは、モノによっては色々な最適化が施されていたりして、一見するとなんのこっちゃよくわからん複雑なソフトウェアです。例えば僕はtensorflowが裏側でやってくれていることを事細かに把握はしてないですし、知らんけど多分複雑です。そういうソフトウェアをコンポーネントとして扱って開発を行っているわけですが、我々の開発とは隔離されたものと言えるでしょう。どんだけ複雑でも開発から隔離されているものはソフトウェアの複雑さに勘定として入れなくてもいいよね、我々が意識すべき複雑さは開発の中でより長く触れる複雑なコンポーネントですよね。

従って、あるコンポーネントの複雑さを、developerが費やす時間で重み付けしてsumを取ることでソフトウェアの複雑さを測ろう。そういう意図のモデルです。

Symptoms of complexity | 複雑性の症状

複雑さがもたらす症状には大きく分けて3つの症状があります。

Change amplification | 変化の増大

コードを改変する際により多くの箇所を修正する必要が出てくること。本の中で出てくる具体例としてはwebpageの背景色の例が挙げられています。 以下、二つの例を取ってみましょう。

  1. それぞれのページでbackgroundカラーが定義されている
  2. ある一つの変数を経由してそれぞれのページのbackgroundカラーを定義している(変数は一つだけ)

仮に前者のソフトウェアデザインで背景色を変更しようと思うと、全てのページの背景色を変えにいかなければなりません。ページが数千もあったら溜まったもんじゃないです。反して後者ならば変数に代入するパラメータを変えれば全てのページの背景色が切り替わります。複雑なソフトウェアとは、より多くの変更を要求します。

Cognitive load | 認知的負荷

コードを改変するときに必要な情報を整理するのに多大な時間を要するようになる。これはわかりやすい複雑さですね。例えばC言語は認知的負荷が高いです。C言語を書いたことがない人に説明すると驚かれますが、動的配列とかが他の言語のようには提供されてなくて、malloc/freeを使ってメモリ管理を自分でしなければなりません。仮に確保したメモリをfree(開放)し忘れるとメモリリークが起こってメモリが枯渇したりします。こういう要素に気をつけながらコードを書くのは骨が折れるし認知的負荷が高いです。RubyPythonで同等の処理を記述するよりも多くの時間がかかる、と言って否定する人はあまりいないでしょう。

他にも以下のようなことが認知的負荷を引き起こすと指摘されています。

Unknown unknowns: 何を知らないのか分からない

変更対象が分からなかったり、変更に際して何を考慮しなければならないのかがわからないこと。これが症状の中で最悪のものです。苦し紛れに書いたコードが、結果としてどのような問題をもたらすことになるのかもよくわかっておらず、問題が起きてからそれを知ることとなる...知見の溜まってないOSSなどを利用していると起こったりしますね...

Cause of complexity | 複雑さの原因

これまでは複雑さがもたらす症状について触れてきましたが、そもそもなぜソフトウェアが複雑になるのかについて、最後にまとめます。筆者は複雑さの原因を2つに分類しています。

  • dependency | 依存
  • obscurity | 曖昧であること

dependency | 依存

クラス間の依存が増えて行ったり、いろんなところで変数が共通して利用されだすと、途端にコードを追うのが辛くなります。クラスのレイヤーの話でなくとも、他のソフトウェアと依存していることもあります。例えばnetworkのプロトコル。senderの仕様を変更したら、receiverも同様に変更しなければなりません。他にもあるmethodのパラメータを増やした結果、呼び出しもとも同様に変更をしなければならなかったり、rest APIとかもクライアントサイドが仕様を変更するとバックエンドもそれに合わせて仕様を変えなければならなかったり...など、依存しているものが増えるとソフトウェアは複雑になっていきます。こっちは構造のお話

obscurity | 曖昧であること

こっちはコードの可読性というか命名のお話。名が体を表すコードならいざ知らず、よくわからない変数名を定義していて、他の人にはなんのこっちゃよくわからんソースコードは複雑さの原因になります。ソースコードから曖昧さを排除することでソフトウェアはシンプルになります。

1章2章まとめ

後続の章で述べられているDeep Moduleという概念が大変クリアで美しかったため購入した書籍なのですが、この章も個人的には有意義でした。自分が意識していなかったことが述べられているわけではないですが、暗黙知を形式値に昇華してくれたこと、バラバラの知識・経験的直感をまとめ上げて自然言語にしてくれたこと、それだけで大変価値のある文章でした。引き続きのんびり読んでいきます。

OSI参照モデルとRFC1122

はじめに

先日、非CS学部出身のソフトウェアエンジニアの友達にネットワークのプロトコルについて軽く説明してた時に、OSI参照モデルに則って説明をしていました。 機械工学科から頑張って情報系にジョブチェンジする際にマスタリングTCP/IPを読み込んだのを思い出しながら、喋ってて、内容は以下のようなものです。

  • インターネットプロトコルを構成する要素を以下の7層に区切って考えるモデルである
  • 送信側では、パケットは7層で作られて下の層に降りていきながらラップされていき、受信側では下の層からラップを剥がしながら通信の内容を読み取る。

最後、雑ではありますが、大体正しいこと言ってる気がします。ざっくりしたイメージを共有したところで、具体的な層ごとの説明をしようとしたところで違和感を感じます。

あれ...?プレゼンテーション層とセッション層を代表するプロトコルってなんだ...?

HTTPやSMTPなどは一番上位のレイヤーに位置するという認識はありましたが、ぶっちゃけ5層6層と分離して考えられるのか...?あれ...? トランスポート層TCPUDPプロトコルの違いや輻輳制御などを学んだ記憶があるので説明できる。ネットワーク層IPv4/IPv6、ルーティングアルゴリズムについて勉強した。データリンク層物理層は...CSMA/CD、CSMA/CAとかあの辺のパケットの衝突検知や回避、macアドレスEthernet周りが定義されてるはずだが...うん?ここってEthernetとかは1層と2層跨ってるよな?なんて思いながら言葉に詰まりました。

OSI参照モデルは実装されていない

いろんな文献を参照してるとOSI参照モデルは頻繁に登場します。僕が初めてネットワークをしっかり学んだマスタリングTCP/IPでもOSI参照モデルを利用してネットワークを階層化し、説明をしていました。もちろん4層モデルの存在は知っていましたが、捉え方が違うだけだろうとか思っとりました。全然違いました。OSI参照モデルRFCで採用されていません。RFCで採用されているのは4層モデルであり、OSI参照モデルはあくまで学習用くらいで捉えておいた方がいいようです。wikipediaにも冒頭にそんな説明がありますね*1

ややこしいのは、中途半端に基礎知識みたいな形で広まってるのと、L2スイッチやL3スイッチみたいにOSI参照モデルに則った命名してる機器が普通に存在してたりするので面倒ですね。

RFC1122を読む

ネットワークのレイヤの定義ってどこにあんだ??って思ったら、RFC1122がそれでした。めっちゃ長い。全部読めたもんじゃねぇ。 細かい話は置いておいて、RFC1122*2 はネットワークのレイヤを以下の4階層に分けることを定義してます。

これらの層が果たす役割だったり、要件を定義していくのが、図とか使わず自然言語でずーーーーーーっと続いてます*3。凄まじい。RFCはあまりしっかりと読んだことなかったですが*4、Introductionを読んでいると仕様にもいくつか厳しさのレベルがあって、絶対やんなきゃいけないもの、やった方がいいもの、やっても良いものという風に、言葉使いを定義しています。丁寧ですね。

     *    "MUST"
          This word or the adjective "REQUIRED" means that the item
          is an absolute requirement of the specification.
     *    "SHOULD"
          This word or the adjective "RECOMMENDED" means that there
          may exist valid reasons in particular circumstances to
          ignore this item, but the full implications should be
          understood and the case carefully weighed before choosing
          a different course.
     *    "MAY"
          This word or the adjective "OPTIONAL" means that this item
          is truly optional.  One vendor may choose to include the
          item because a particular marketplace requires it or
          because it enhances the product, for example; another
          vendor may omit the same item.

他にも、要件の追加説明にはDISCUSSIONやIMPLEMENTATIONなんてラベルの項目があって、こういうことが起こるとこんな問題が生じるから、実際に実装するにはこうするといいよ、みたいな実装者への提案もあったりしてます。こんな感じなのか。当たり前ですが実装と1対1で対応するほど詳細な定義はされてなくって、ある程度含み持たせてんだなぁとなりました。この定義をメールでやりとりしながら決めて、で、スーパーハッカーみたいな人たちがその実装作って、実際それが動いてThe Internetを支えてるって、めっちゃcoolですな....

ただ自然言語で説明されてもイメージがつきにくいところがたくさんあって、世に出回ってる教科書や資料なんかを見た方が普通に時間効率はいいですね。大学院の授業で使ったComputer Networkingは相当に丁寧でよかった記憶しかないのでリファレンスにええっすよとだけ推しとこ。

終わり

自分はネットワークが専門でもないですし、ネットワーク関連のR&Dプロジェクトに大学院を出た後アサインされる予定も今のところないです。仕事はアプリケーションよりのエンジニアリングがメインなので。実務の中ではRFCを直接参照するよりも他の媒体で情報収集することが多そうですし、APIを叩くインターフェースが綺麗に簡単に使えるように中身をラップしてくれているので、ぶっちゃけRFCで実装されてようがOSIで実装されてようが気になりませんでした。ただ、他人に何かを説明する場面で嘘つきそうになったのに気づけてよかったっす。知識の整理にもなったし。

あと低レイヤーオタクなのでこういうの読んでると、いつかエンジニアとして成長してネットワークのプロトコル実装するお仕事とかできるくらい強くなりたいなって思いました。誰かスカウトしてください。

*1:wikipediaなんて参考にすんなって人が多いですが9割5分くらい正しいこと書いてる(分野による)んで論文書いたり公式な声明書いたりしない限りはかなり参考になると思ってます。

*2:日本語版の方が読みやすい

*3:長い論文だと100P近いものってありますし、見慣れないわけでもないんですが、フォーマットがcssとか知らなそうなアレでアレがアレで長く感じるのかな

*4:このRFCも隅から隅まで読んだりはしてません

GCS上のオブジェクトを一部だけダウンロードする

はじめに

自分用メモ。欲求は以下

  • GCS上にある数GBとかの大きめのデータの中身を確認したい
  • でもローカルに丸ごと引っ張ってくるのは嫌
  • linuxのheadコマンドみたいに一部だけみたい

comand line(gsutil)

catコマンドのオプションを見てみたら2秒で解決した

$ gsutil cat --help
〜〜略〜〜
OPTIONS
  -h          Prints short header for each object. For example:

                gsutil cat -h gs://bucket/meeting_notes/2012_Feb/*.txt

              This would print a header with the object name before the contents
              of each text object that matched the wildcard.

  -r range    Causes gsutil to output just the specified byte range of the
              object. Ranges are can be of these forms:

                start-end (e.g., -r 256-5939)
                start-    (e.g., -r 256-)
                -numbytes (e.g., -r -5)

              where offsets start at 0, start-end means to return bytes start
              through end (inclusive), start- means to return bytes start
              through the end of the object, and -numbytes means to return the
              last numbytes of the object. For example:

                gsutil cat -r 256-939 gs://bucket/object

              returns bytes 256 through 939, while:

                gsutil cat -r -5 gs://bucket/object

              returns the final 5 bytes of the object.

-hでheader(先頭)をよしなにとってきてくれる。  -rでrangeを指定できる。指定はバイトで指定する

Python

from google.cloud import storage

def gcs_head(bucket_name: str, file_path: str, character_code: str) -> None:
    """
    GCS上のcsvの先頭1MBだけをダウンロードして出力する
    
    input
    bucket_name: str
        gcsのbucket
    file_path: str
        オブジェクトへのパス
    character_code: str
        オブジェクトの文字コード
        downloadしたものはバイト型なので標準出力しようと思うとdecodeする必要がある
    """
    gcs_blob = storage.Client().bucket(bucket_name).blob(file_path)
    head: bytes = gcs_blob.download_as_bytes(end=100000)
    print(head.decode(character_code))


gcs_head(bucket_name="proj_hoge", file_path="sample.txt", character_code="shift-jis")

Blobsのドキュメントを見てると、ダウンロード系のメソッドにはstartとendの引数があって、読み込みの開始位置と終端をバイトで指定可能。