やる気がストロングZERO

やる気のストロングスタイル

個人開発でも業務クオリティを目指すのが重要

今、個人開発で「俺が考える最強の契約管理システム」の作成作業をしている。
作成作業しながら「個人開発でも業務クオリティを目指すのが重要だな」と感じたのでその理由を書く。

個人開発は業務と違う方向で良い経験を詰める場であるが、いくらでも手を抜けてしまえるので、業務クオリティを目指すことで良い経験を積めるように仕向ける必要がある。

ちなみにこの作成作業はYouTubeで公開していて、作業リポジトリGitHubにある。

[YouTube]
www.youtube.com

[GitHub]
github.com

個人開発は得た知識を存分に試せる場所だ

なんで個人開発をするのか?

なぜなら業務での開発では、自分の思い通りに作業できない事が多いからだ。

これは組織の一員として開発を行うからには当然だ。
すべての責任を自分で負えるわけではないからだ。

しかし、僕にもいろいろ試したい設計や書き方や技術がある。
これらを試せる「誰にも邪魔されない環境」とは個人開発しかない。

「あのときレビューで反対された設計で実際に突き進んでいたらどうなったのか?」等、存分に試すのである。

手を抜いてしまうという個人開発の罠

個人開発は自由なので、いくらでも手を抜けてしまう。
「自分しか使わないし、ログイン機能はパスワード固定にしておいたら良いか」
「自分しか使わないし、httpsでなくてもいいか」
みたいな感じである。

業務だったらどんなめんどくさい実装も強烈なプレッシャーによってやらざるを得ない状況になり、それがスキルとして蓄積されていく。
そして大抵は面倒くさい事が必要なスキルだったり必要な機能なのである。

そういうものを避けて開発を行っても大したものは出来ない。

僕は今まで個人開発でいくつかのサービスやらツールやらを作った。

  • エロ動画リンク収集ボット
  • 集めたリンクでアフィリエイトサイトの構築
  • 芸能人に似たAV女優を探して紐付けるシステム
  • 株価をトラッキングしてある条件を検出したら教えてくれるシステム
  • twitterで特定のハッシュタグで投稿された画像リンクを収集するシステム
  • twitterで収集した画像リンクを一覧表示させるサイト
  • 持ち物リスト共有するサービス

これらは上記で言う手を抜いた個人開発で、動きはしているもののサービスとして低機能で完成度は低い。

しかし無意味だったかと言えばそうではなくて、お小遣い程度の稼ぎと、エンジニアとしての転職時には大いに役立った。

また、得られた一番大きなものとして、
「僕はサービスを完成させられる」という自信を得る事ができた。

この経験から「個人開発の目標」はその人のスキルによって目指すべき段階があると考えるようになった。

まずは「完成させること」を目指し、自信がついたら「クオリティ」を目指す

僕がそうだったのだが、まだ経験が浅く何かを作りきった事がないなら「完成させる事」を目標にする。
完成させる為に最短距離を行く。できるだけ機能は削ぎ落とす。

自分が完成させられるかもわからない状態で、細かい部分にこだわっていてもモチベーションが続かない。
完成に近づく事がモチベーションになる。

そうして「完成させられる自信」がついたら、次はクオリティを目指す。

業務で行うのと同レベルに妥協の無いものを作る。
実際に使えるものを作る。
高品質な機能が追加されることがモチベーションになる。

ただし、かけられる時間は少ないので機能は絞り込む。

業務でもなかなか出来ない経験を個人開発で得る

例えば「ログイン機能」だが、大抵の場合は既に実装されていて、なかなか自分で作ることって少ない。

なんとなく作り方は知っていても、実際に作るとなると「パスワードリセットはセキュリティ的にどのようにすればいいのか?」意外にに調べる事は多い。

「作り方を一応知っている」と「実際に実装したことがある」ではかなり違う。
こういう経験を個人開発でどんどん得ていく。

こんなセッションIDは嫌だ

ログイン周りってだいたい既に出来上がってて自分で実装したことなかった。
知識としてはなんとなく知ってたけど「俺が考える最強の契約管理システム」でもログイン機能つけないといけないなーと思ってセキュリティを含めて改めて勉強したのでメモ。

参考)まだ前半しか読んでないけど、説明が丁寧で読みやすくて、内容自体も面白いしおすすめです。

ログイン状態はセッションIDで管理してる

ログイン状態の保持は、ブラウザではcookieとsessionを使って実現してる。

A君がログインページでログイン認証が成功したら、サーバー側のsession情報にA君のuser_idを覚えさせる。

session_id data
1 user_id = 10 (A君のid)
2 user_id = 121(他の人のid)

そして、ブラウザのcookieにsession_id = 1を覚えさせる。

A君が自分のプロフィールページにアクセスするとき、ブラウザはcookieをサーバーへ送出する。

ブラウザは受け取ったcookieの中からsession_id = 1を見つけ、それを使ってsession情報からuser_id = 10を見つける。

ブラウザはuser_id = 10のプロフィール情報を表示したページをブラウザへ返す。

セッションIDがバレるとマズい

ここで、B君が何らかの方法でA君のsession_idが1であることを知ったとする。

B君は自分のブラウザを操作してcookieにsession_id = 1をセットし、サーバーへアクセスすると、、

ブラウザは受け取ったcookieの中からsession_id = 1を見つけ、それを使ってsession情報からuser_id = 10(A君のid)を見つける。

ブラウザはuser_id = 10の(A君の)プロフィール情報を表示したページを(B君の)ブラウザへ返す。(情報漏えい)

だからセッションIDはバレてはいけない。

こんなセッションIDは嫌だ

プロトコルがHTTPだ

HTTPは暗号化されていないのでネットワーク経路上でsession_idが丸見えだ。
HTTPSであれ。

URLに付加されている

こんなやつ。
https://example.com/your_profile/?session_id=xxxxxxxxx

ブラウザをのぞき見たり、履歴からセッションIDがバレる。

他にも、B君がA君にこのurlを踏ませてログインさせることで、B君はA君のセッションIDがxxxxxxxであることを知っている状態になる。
※ただしサーバー側がログイン時点でセッションIDを変えていたら漏洩は防げる

セッションIDが連番だ

セッションIDがログイン順に連番で発行されていると、セッションIDを予測することが出来る。
例えば、B君がログインしてセッションIDが10だったので、その後ログインしたA君のセッションIDは11だろう、と予測が出来てしまう。
セッションIDは予測出来ないものでないといけない。

「予測できない」というのを自前で用意するのは難しい。
このあたりは暗号まわりと関連が深くて「自前で仕組みを作らず、用意されている仕組みを使え」という感じ。

関係ないサイトにもセッションIDが送出されている

アマゾンへ送出されるべきセッションIDが楽天へ送られてしまうと、楽天の人にセッションIDがバレてしまう。

通常はドメインで送出先が制限されているのでそんなことは起こらないが、
設定によっては、example.comへのセッションIDが、a.example.comへも送出されることはある。

仮にレンタルサーバー事業者などで、a.example.comが別の管理者のサイトになっている場合、
example.comのセッションIDがa.example.comの管理者にバレる事になる。

逆に、関係ないサイトのセッションIDを送出してしまうケースがある。
クッキーモンスターバグ」というバグが原因で、無関係のサイトのセッションIDを送出させられることで、セッションIDを強制させられ使用セッションIDがバレるというケースもある。

ログイン先サイトが脆弱だ

ログイン先のサイトのセキュリティ意識が低いと、様々な方法でセッションIDを知ることが出来てしまう。

攻撃者が用意したurlを踏ませるだけでセッションIDがバレてしまったりする。

vimとtmuxの設定メモ

エンジニアになった当初からターミナル作業を華麗にやることに憧れている。

現状の設定と操作方法をメモっとく。

以下、参考にしました

https://blog.craftz.dog/my-dev-workflow-using-tmux-vim-48f73cc4f39e

マスタリングVim

マスタリングVim

事前準備

fishとfzfとghqをインストール

【覚書】Ubuntuのシェルをfishにした - やる気がストロングZERO

Ackをインストール

sudo apt-get update
sudo apt-get install ack-grep

dotfiles用意

ドットファイルのクローン

ghq get https://github.com/mixmaru/dotfiles.git

各種設定ファイルをリンク

ln -s  ~/ghq/github.com/mixmaru/dotfiles/.vimrc ~/.vimrc
ln -s  ~/ghq/github.com/mixmaru/dotfiles/.tigrc ~/.tigrc
ln -s  ~/ghq/github.com/mixmaru/dotfiles/.tmux.conf ~/.tmux.conf
ln -s  ~/ghq/github.com/mixmaru/dotfiles/ide ~/bin/ide

使い方メモ

fish

gitリポジトリ移動
ctrl + g

コマンド履歴検索
ctrl + r

ディレクトリ移動
ctrl + o

tmux

画面分割
ide

ペイン移動
ctrl + b h 左移動
ctrl + b j 上移動
ctrl + b k 下移動
ctrl + b l 左移動

window移動
shift + ctrl + 矢印キー

vim

ディレクトリ一覧表示
-

ファイルやバッファから開くファイルを検索
ctrl + p

指定箇所に移動(vim-easymothon)
\\ w 前方移動
\\b 後方移動

検索
:Ack 文字列

折りたたみ
zR 全部開く
zM 全部閉じる
za トグル

画面分割
ss 縦分割
sv 横分割

画面移動
ctrl + w h
ctrl + w j
ctrl + w k
ctrl + w l

タブ切り替え
shift + tab 正方向
shift + ctrl + tab 逆方向

「良いコード」と「良くないコード」の特徴

自分が思ってる「良いコード」と「良くないコード」の特徴を書きなぐってみる。

関数

良い

引数と返り値を見て、何をしているのかなんとなくわかる。

c = add(a, b)

# aとbが加算されてるっぽい

良くない

引数と返り値を見ても、何をしているのかよくわからない。

c = add()

# なにかを加算してるらしいけど、何が加算されているのか、、(内部でdbからデータを取得して加算して返してたりする)

どういうこと?

処理の詳細を隠して一言(関数名)で表される事で抽象化を行って理解しやすいコードにするのが関数の一つの役目。
中身を見ないと理解できない関数は、中身が隠蔽されている分理解しづらくなっているだけなので良くない。

関数は外から見てやっていることが、どういう実装なのかは考えずに解ることが大事だとおもう。

カスタマイズ

ある一連の処理にて、実験的に一部の処理をスキップさせたい。

良い

単に該当部分をコメントアウトし、値が帰ってきたことにすれば意図通りに実行できる。

良くない

どこが該当処理なのかよくわからない。
ここだと思う部分をコメントアウトして実行すると、別の場所でエラーが出る。
見てみるとコメントアウトした処理が実行されたときにDBにデータが挿入されており、その値がないので後続処理でエラーになっていた。

どういうこと?

良いコードは処理と処理が独立していて、ある処理が別の処理に直接影響していることがない。(処理の結果値を次の処理がもらうことはあるが、値が必要なだけで処理自体が必要なわけではない)
だから、次の処理のために仮の値を用意してやることで簡単にスキップすることができる。

良くないコードは、ある処理の副作用が別の処理に依存されていたりして、処理を書き換えることが簡単にはできない。中を調べて何が行われているかを知り、別の処理に影響がでないように変更してやる必要がある。

仕様確認が簡単にできる

あるapiのバリデーションルールを知りたい。

良い

そのapiのバリデーション定義見てすぐ理解できる。

良くない

そのapiのバリデーション定義を見ると、複数のapiから共有して使われており、多数の条件分岐が存在していた。リクエストパラメータによる分岐もあり、どういう値が渡ってくるのかも把握しないといけない(しかしそれは今回の対象apiでは渡ってこないものなのかもしれない)。 先頭から条件分岐を追っていかないとルールが把握できない。。

どういうこと?

設計されずにその場しのぎでコードがいじられると陥る。
アドホックにif分岐で対応され続けた結果、論理的な構造を持たないコードの塊になってしまっている状態。

リファクタリング

メソッドの定義位置に違和感が出てきたので別のクラスにメソッドを移動させたい。

良い

メソッド定義場所を移動させ、呼び出し元を書き換えて、少し調整すれば移動できた。

良くない

メソッド定義場所を移動させ、呼び出し元を書き換えて、少し調整したがエラーがでた。
調べると内部でdbデータの読み書きを行っており、そのためにライブラリを使っているが、移動先にはライブラリがインポートされていない。
移動先にライブラリをインポートして、DB接続初期化処理を用意してやらないといけない??

どういう事?

何らかの処理メソッドなのに、内部でdbアクセスまでやってる(もしくは別のオブジェクトに副作用を与えてる)から、保存ライブラリや別オブジェクトに依存した形になってる。

計算して結果を返し、別オブジェクトへの干渉は別途やっていれば(つまり一つのことだけ行うようにしていれば)メソッド定義場所の変更は容易なはず。

テーブル定義

良い

テーブル構造とデータをみれば、コードを追わなくても状態が把握できる。 どのようにデータを変更すればどのような状態になるのか、コードを追わなくても解る

良くない

テーブル構造とデータの中にフラグが多数あり、どのフラグがどうなっているかによって、データの見方が複雑に変化するので理解が追いつかない。
「こういうデータは存在しえない」とか「typeが○○のときoptionには××の情報が入っている」とか、データの見方に暗黙のルールがたくさんある。

どういうこと?

  • テーブル設計が詰めきれておらず、コード側で補完してる。(だから処理と組み合わせて見ないと理解できない。)
  • 処理のためのデータが存在してでそれがノイズになってる。(それはコードを見ないとどのように使われているのかわからない)

こういうのは大抵コード側も複雑な事になってる。。

導出できる値の扱い方

例えば「消費税」をどのように扱うか?

良い

算出メソッドが定義されてて、必要なとき実行し定価から算出されるようになっている。

良くない

算出した消費税額を保持していて、定価が変化(値引きなど)するたび再計算して保持し直している。

どういうこと?

こういう事をする動機としては「一旦計算した値を保持することで何度も計算せずにすむ」とかなのかなと思うが、
だいたい管理できなくなって事あるごとに再計算処理を実行するような感じに陥ってたりする。

例)
* ここで定価を変更した、このあと消費税の再計算を実行しているかよくわからず不安だから再計算を実行しておく。
* ここで消費税が必要だけど、どこかで消費税計算実行が漏れていたら怖いから再計算処理を実行しておく。

結果、なんどもなんども消費税の再計算が不要に行われてたりする。(何度も計算せずに済むはずだったのに。。)

つまり、データの整合性を自力で取っていかないといけないので複雑正が増している。

テックリード・アーキテクトやってみた感想

ここ数ヶ月、業務でテックリードとしてシステム構築の設計と実装のリードをやってきて、区切りができたので、

  • 良かったこと
  • 良くなかったこと

をまとめてみる。

自分の役割(全体設計・実装作業のリード)

職場ではテックリードって役割になっているけど、別に高度な技術を先導するとかはしてなくて、主に設計周りをやってるので「アーキテクト」のほうがしっくりくるかも。

システムの全体設計(言語選択・ミドルウェア選択・構造構築など)と、完成に向けての実装タスクの切り出しと、開発メンバーへのタスクの依頼とコードレビューを行った。

やって良かったこと

開発環境構築を自動化した

最初に開発環境の構築を一人で行った。
docker-compose upで必要な実行環境や開発用ミドルウェアがすべて立ち上がるようにした。

構築には地味に時間かけたけど、誰でも簡単にコマンド一発で開発環境が整うようにしたおかげで途中参加メンバーもスムーズに開発業務に入れたし、なんか環境がおかしくなっても基本的には作り直せば治るので、このあたりのコスト削減効果はあったなと感じた。
メンバーが多くなればなるほどここの部分は生きてくると思う。

また「外部サーバーとやり取りする部分」などの実装はテスト実行することが難しいが、外部サーバーを模したコンテナを用意してテスト実行しながら実装を進めることが出来るようにし、スムーズに実装してもらうことができた。

全体設計を自分一人に任せてもらえた

全体設計を自分一人に任せてもらえたのは良かった。

たぶん、複数人で検討すると意見が割れて落とし所を探す感じになり、一貫した設計ができなかったのではないかと思った。
また、一人でやることで責任の所在がハッキリし「設計が破綻したら自分の責任」と思うと死ぬ気で考えることができた。

細かめの粒度で実装タスクを切り出ししたことで品質管理が楽だった

全体設計を終えたあと、必要な処理をできるだけ単純な単体処理に切り出して実装タスクにし、テストコードも書いてもらうようにお願いした。

上がってきたコードをレビューする際

  • テストコードにより、期待通りの結果が得られているかが判断しやすい
  • 処理が単純なのでコードの理解がしやすくレビューもしやすい
  • 仮にコードがマズくてもその影響範囲は関数内に収まるので破綻はしない

のような感じで、ある程度ラフにレビューを進めることができた。

良くなかったこと

自分がボトルネックになった

「設計」「タスク切り出し」「レビュー」をやってると、実装メンバーの開発速度に追いつかれてしまう事がちょいちょいあった。
このプロジェクトに専任出来たらもう少し速度を挙げられた気がするけど難しかった。

つつかれて切り出し切れていないタスクをざっくりと依頼するようなことも増えてしまったが、実装メンバーの力量によってはざっくり渡したほうが速度が上がることがわかった。このあたり調整のしどころかも。

DB設計から関わりたいと思った

自分に話が回ったきた時にはある程度DB設計が済んだ状態だった。
たぶんそれは今回の目的に特化した構造になっていて、それ故テーブル構造はシンプルなんだけど、 その時想定されていた事情外の事が発生すると結構コード側で頑張る感じになってしまう。

僕個人はテーブル構造とか、システム構造はできるだけ汎用的にしたいと思っていて、 こうしておくと考慮外の事情(主に運用が始まってから)にも低コストで対応できるけど、 構築自体には工数がかかる。

特定の目的に特化させたテーブル構造、システム構造は構築は早いけど、考慮外の事が発生すると対応が難しくてここで工数がかかる。

僕は多分汎用性に寄りすぎた思想なので、もう少しバランスをとった考え方が出来るようになったほうがいいのかも。

まとめ

メンバーに恵まれたのもあり「システムを作る」事自体は問題なく行えると感じた。

ただし「プロジェクトが成功するかどうか」というのは、もう少し上のレイヤーの部分での立ち振舞による気がしてきた。

【Go】interface{}、reflectionについて調べたメモ

Goで「型は異なるが共通する処理(型が違うだけで、行いたいことは共通)」を書こうとしたとき、どうやればいいのかよくわからなかった。

C#だとジェネリクスとか使う感じのやつ。

Goだと引数をinterface{}でうけとって、reflectionを使ってなんやかんややる感じっぽい。

reflectionをどの様に使えばどのようなことができるのかを調べた
参考)https://blog.golang.org/laws-of-reflection

サンプルケース:「map[フィールド名]値」を使ってフィールド値をセットする関数を作る

type BaseHuman struct {
    Id int
}

type Human struct {
    Name string
    Age  int
    BaseHuman
}


// 「map[フィールド名]値」を使ってフィールド値をセットする。どんな型でも処理できる。
func SetValueFromMap(i interface{}, values map[string]interface{}) {
    structureElem := reflect.ValueOf(i).Elem() // iはポインタ型なのでElem()で値を取得する。
    for key, value := range values {
        field := structureElem.FieldByName(key) // keyでフィールドオブジェクトを取得する
        field.Set(reflect.ValueOf(value)) // Set()で値をセットする。(値がハッキリしてるなら(例えばintとか)SetInt()とかを使っても良さげ)
    }
}

// 実行テスト
func TestSetValueFromMap(t *testing.T) {
    human := Human{}

    // mapでフィールド名と値データを用意
    values := map[string]interface{}{
        "Id":   100,
        "Name": "太郎",
        "Age":  10,
    }

    // 構造体のポインタとデータmapを渡すと、構造体の対応するフィールドに値をセットしてくれる。
    SetValueFromMap(&human, values)
    assert.Equal(t, 100, human.Id)
    assert.Equal(t, "太郎", human.Name)
    assert.Equal(t, 10, human.Age)
}

postgreSQLのtimestampはtimezone情報がないのでtimestamp with time zoneを使うのが良さそう

postgres のtimestampはタイムゾーンをもってない 。
だからデータを入れるとき、取得するときにタイムゾーンを気にしないといけない。
timestamp with time zoneで定義すべきかなと思う。
基本的にシステム内部で時間を扱うときは統一的にUTCで扱い、出力時に用途に応じて変換してやるのが良いとおもってる。

参考)

www.m3tech.blog