夢のかけら

Goエンジニアの技術ブログ

npm installとnpm ciの違い

f:id:lampler:20211023102547p:plain

npm install

npmi ipackage.jsonを見てライブラリをインストールする。pacakge.jsonのバージョン指定には幅があるため、npm iする時期によってはpackage.json.lockを書き換えてしまう。 つまり完全に同じ開発環境を再現できない(可能性がある)。これはnpm iの大きな問題点と言える。

npm ci

一方でnpm cipacage.json.lockからライブラリをインストールする。(これがnpm iとの最大の違い)

具体的には↓の3つをやっている。

  1. node_modules ディレクトリの削除
  2. package-lock.json と package.json の整合性のチェック。違ったらエラーを吐く。
  3. package-lock.json から node_modules を再現

npm iではなくnpm ciを使うことで完全に同じバージョンのライブラリをインストールすることができる。 git clone 直後などはnpm ciの方が良さそうだ。

【Docker】Next.js + TypeScriptの環境構築

試行錯誤して0からDockerfileを書いた記録です。

f:id:lampler:20211010220842j:plain

まず適当にDockerfileを書く。

FROM node:16-alpine
WORKDIR /app

ビルドしてコンテナに入る。

$ docker build .
$ docker run --rm -it -p 3000:3000 a5e52242bb4f sh

コンテナの中でサーバーを立てる

/app # npm i create-next-app

/app # ls
node_modules       package-lock.json  package.json

/app # npx create-next-app sample_app --typescript

/app/sample_app # npm run dev

> sample_app@0.1.0 dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000

http://localhost:3000 へGo!とりあえず無事に動いてる。

f:id:lampler:20211010210341p:plain

マウントする

$ docker run -it --rm -p 3000:3000 -v `pwd`:/app 3ff9c4e9af64 sh

/app # npm i create-next-app

/app # npx create-next-app sample_app --typescript

/app # cd sample_app/
/app/sample_app # npm run dev

> sample_app@0.1.0 dev
> next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000

これでホストのファイルシステムと同期 & ホットリロードできるようになっている。 試しにホストのpages/index.tsxを変更すると即座に変更が確認できる。

Gitで管理する

ホスト側では プロジェクトのルートディレクトリにDockerfileがあり、同階層に sample_appディレクトリがある。ルートディレクトリ上の node_modulesおよびpackage.jsonは コンテナ上で npm i create-next-appを実行した時に作成されたものである。つまりもういらない。Dockerfilesample_appの中にいれてしまう。

$ mv Dockerfile sample_app/
$ cd sample_app
$ ls 
Dockerfile        next.config.js    package.json      styles
README.md         node_modules      pages             tsconfig.json
next-env.d.ts     package-lock.json public

このsample_appをGitの管理下に置く。適当にコミットしてpushする。

$ git init
$ git add .
$ git commit -m "first-commit"

Git Cloneをする

$ git clone https://github.com/big-of-big/sample_app
$ cd sample_app

このディレクトリには node_modulesがない。つまりコンテナなの中ではパッケージをインストールする必要がある。

 $ docer run -it --rm -v `pwd`:/app/sample_app -p 3000:3000 80ce3b29c8c7 sh
/app/sample_app # npm run dev

> sample_app@0.1.0 dev
> next dev
sh: next: not found

ということで npm installpackage.jsonから必要なパッケージをインストールする。

$ /app/sample_app # npm install

ようやくサーバーを立てる!

/app/sample_app # npm run dev

> sample_app@0.1.0 dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000

これで一応開発はできる?

一応これでチーム開発はできると思うけど、毎回長いrunコマンド打って、コンテナの中に入ってnpm installするのは流石にしんどい。 長いコマンドは docker-composeリファクタリング、後者の問題はDockerfileを変更して対応する。しかし長くなるので終わる。

Dockerの備忘録

  • コンテナの中に入るときは -v オプションでhostのファイルシステムと同期する
  • $ docker run -it --rm -v (ホストの絶対パス):(コンテナの絶対パス) -p 3000:3000 <イメージ名> shでコンテナの中に入る
  • Dockerfileは1つのプロジェクトに1つだとは限らない。いくつでも置ける。

ホストのイメージの一覧を確認する

$ docker images

アクティブなコンテナを表示

$ docker ps

全てのコンテナを表示

$ docker ps -a

ポートをつなげる

$ docker run -it --rm -p 8000:8000 <イメージ> bash

runについて

--rmでコンテナをexit後に消す。つまり一度きりのコンテナ。

$ docker runの詳細

  1. イメージを探す→なかったらdocker hubからpull
  2. イメージからコンテナを作成
  3. 中のプログラム(デフォルトのCMD)を実行
  4. exitする

つまり runをするたびに新しいコンテナが作成される

コンテナの削除

$ docker rm <コンテナ>

コンテナを停止

$ docker stop <コンテナ>

全消し

$ docker system prune

Dockerfile

  • イメージにタグ(別名)をつけるdocker build -t <名前> .

  • ベストプラクティス:レイヤーは最小限にする。

  • レイヤーを作るのは ADD, RUN, COPY

  • Dockerfileを書いている途中ではRUNを連発するほうがいい。なぜならキャッシュを使用することでビルドが早くなるから。

  • CMD: デフォルトのコマンドを定義する。Dockerfileには最後に一回だけ書く。一回しか使えない。

  • ADDとCOPYを使うことで、build contextのファイルをコンテナに持っていくことができる。99%はCOPYでよい。ADDはtarの圧縮ファイルを解凍するときに使う

  • Dockerfile内ではcdを使わない。代わりに WORKDIRを使う。cdを使うと&&で繋げないとルートディレクトリに戻ってきてしまう。

  • Dockerfileを変更すると必ず再度ビルドする必要がある。さもないと古いイメージが使われてしまう。

docker-compose

  • runのコマンドが長い or 複数のコンテナをまとめる役割で使用する。docker-compose.ymlに指定するパスは必ず相対パスを使う。

  • -ittty: true(綺麗に表示する) stdin_open: true(入力できるようにする) オプションである。

  • $ docker-compose upはビルドもできる。古いイメージを使わないためには--buildを使う。もしくは $ docker-compose build つまり↓の手順

  • 1 Dockerfileからイメージを作成

  • 2 作成したイメージからコンテナを作成
  • 3 コンテナを起動してデフォルトのコマンドを実行

Dockerによる開発

  1. ホストのファイルとコンテナのファイルを同期
  2. ホストの環境では原則何もインストールしない
  3. コンテナの中に入っていろいろインストール && サーバーを立てる
  4. docker-compose run <サービス名>でコンテナに対してコマンドを実行する
  5. ホストでファイルを編集→コンテナのサーバーで動作確認

Goに入門したので備忘録を書く

パッケージ

  • 同じパッケージに属するなら、そのパッケージ内で変数や関数を参照できる。
  • つまりパッケージとは1つのファイルを複数に分割する仕組み。

文字列

  • Goの文字列はかなり特殊なので挙動に注意する。
  • イミュータブル。
  • 値型なのに関数の引数に渡してもコピーがされない。
  • だからポインタがいらない。

マップ

  • マップは存在しないキーを指定すると初期値が返ってきてしまう。
  • これを防ぐために引数を2つ受け取り、エラーハンドリングを行う。
m := map[int]string{ 1: "A", 2: "B"}
if _, ok := m[1]; ok {
  // mにはキー1が存在する
}

チャネル

  • チャネルとはゴルーチン間でデータを共有する仕組み
  • チャネルから受信する=他のゴルーチンからデータが来るのを待つ
  • 双方向のチャネルは受信専用・送信専用、どちらの型にも代入できる

構造体

  • Goの構造体は省略記法として、ポインタと実態を気にすることなくメソッドを呼び出せる。値も代入できる。
  • 構造体に定義するメソッドのレシーバは原則ポインタ型にする。そうしないと破壊的変更ができない。
  • 構造体のフィールド(キー・インスタンス変数みたいなやつ)は初期値を持っている。
  • フィールド名 型名が同じときはJSのオブジェクトと同じ省略記法が使える。
  • 省略記法を使うと、hoge.huga.namehoge.nameのように階層を飛ばしてアクセスできる。

値型と参照型

  • Goの型は値型と参照型に分類される。値型は関数の引数として渡すとき、コピーとして渡されてしまう。値型を参照渡しとして破壊的変更を関数内で行うためには、ポインタを使う。
  • 参照型はスライス、マップ、チャネルの3つのみ。自分で定義する独自型も含めて、他は全て値型となる。
  • スライス、マップ、構造体では構造体の型名を省略できる。
m := map[int][]string {
    1:  {"hoge"}, // []string{"hoge"}と同じ
    2: {"huga", "piyo"}
}

独自型

  • type (定義する型) (既存の型)のような文法。
  • 独自型を作ったり既存の型にエイリアスを与えることができる。

インターフェース

  • インターフェースとは「異なる型に共通の性質を付与する」ための機能。ダッグタイピングと似ている。
  • あるインターフェースにあるメソッドを全て実装すると、自動的にそのインターフェースに属することができる。
  • インターフェースによって、第二・第三の型を与えることができる。

【Golang】構造体はなぜポインタ型に値を代入できるのか

f:id:lampler:20210928210714j:plain

構造体がわからない

↓のような超絶簡単なコードだが、どうもしっくりこなかった。

type Person struct {
    Name  string
    Age   int
}

func main() {
    lamp := new(Person)
    fmt.Println(lamp)  // => &{ 0} (ポインタ型)

    lamp.Name = "lamp" // どうしてポインタに代入できるの!?
    fmt.Println(lamp)  // => &{lamp 0}
}

lamp.Name = "lamp" ここがよくわからない。ポインタとは変数の型と変数が格納されているメモリのアドレスのこと。 このコードは変数の実態に直接代入しているように見える。

これなら理解できる

lamp := Person{}
lamp.Name = "lamp"
fmt.Println(lamp) // => {lamp 0}

結論:おそらくGoの省略記法

これは省略記法ではないかと思う。(ソースを見つけられていないので真実は不明)

lamp := new(Person)
fmt.Println(lamp)  // => &{ 0} (ポインタ型)
fmt.Println(*lamp) // => { 0}
(*lamp).Name = "lamp"
fmt.Println(lamp) // => &{lamp 0}

省略せずに書くと (*lamp).Name = "lamp"だが、 lamp.Name = "lamp"でもOKになってるのではないだろうか。

配列へのポインタ

配列でも同じことが起こる。 ↓のコードではpがポインタ型であることをコンパイラが検知して、自動的に (*p)[0]に変換してくれる。

p := &[3]string{"a", "b", "c"}
fmt.Println(p[0])    // => a (省略記法)
fmt.Println((*p)[0]) // => a (こちらが本来の書き方)

エンジニアに転職して5ヶ月が経ちました

f:id:lampler:20210829210543p:plain

はじめに

フィヨルドブートキャンプを卒業してエンジニアになって5ヶ月が経ちました。 仕事についていけるの?仕事は楽しい?エンジニアになってよかったこと・悪かったことなどを書いていこうと思います。

仕事はついていけるのか?

ついていける。が、とても難しい😅 でも基本的に1人でタスクを完遂できる。たくさん質問して大量に先輩の時間を奪う、マイナスの戦力では決してない。 自分に関しては、難しいタスクを渡されても(泣きながら)こなしてしまうので、「お、こいつ1年目やけどめっちゃ使えるやん!」ってことになって、さらに難しいタスクが降ってくる。 この好循環で自分がいくら成長しても、全く仕事が楽にならない。という状況が今の近況である。常に「カラダもってくれよ!! 3倍界王拳だっ!!!!!」という感じ。なので、体が潰れないように毎日9時に寝て、5時に起きて、ランニングして、朝風呂に入って、というようにかなり健康に気を使っている。健康は最大の資産。健康があれば何でもできるけど、健康を失ったら何もできない。健康を損なわないギリギリのラインで、仕事に全集中している。

エンジニアになってよかったこと

スーツを着る必要が無くなったこと

自分はスーツが大嫌い。小・中・高とずっと制服で、小学校の時から常に着心地の悪い服装にストレスを感じていた。私服ならばもっと高いパフォーマンスを出せるのに...とずっと思っていた。 年収500万の私服勤務と年収1500万のスーツ勤務なら迷わず前者を選ぶくらいだ。それくらい人生の大半をスーツで過ごすのは自分にとっては辛いことだった。スーツを全て捨てただけでQOLが爆上がりした。

働く時間が自由になったこと

今の職場はコアタイムがない。出勤・退勤の時間を打刻することもない。完全な成果主義である。私はだらだら仕事をするより、短期集中したい人なので本当に働きやすい。 子どもが2人もいるので子どもの世話もしやすい。本当に助かっている。

働く場所が自由になったこと

エンジニアの仕事は在宅勤務と非常に相性がいい。出社しないとできない仕事はないと思う。今は3LDKで家賃が65000円くらいの田舎に住んでいる。昔は会社の場所に合わせて、住みたくもない場所に住む必要があった。 これからは生涯好きな国・地域に住んでいい。これも幸福度が爆上がりした。

給料が上がったこと

エンジニアになる前にやっていた仕事は完全な年功序列だった。そして昇級ペースが亀よりも遅かった。新卒で入社して12年働いてやっと年収400万になるくらいに低かった。とても家族を養っていくのは無理だった。私の前の手取り給料は15万だった。 貧乏すぎて常に嫁と喧嘩していた。子どもが生まれても金がなさすぎて離婚しそうになるくらい揉めていた。今はエンジニア1年生で年収400万は超えた。これで嫁と揉めることはなくなった。心に余裕ができて毎日がとても楽しい。年齢ではなく技術力一本で評価してくれるので嬉しい。完全成果主義は高いパフォーマンスを出せる私と非常に相性がいい。

自分の市場価値が毎日上がること

未経験で転職活動をしていたときは市場価値は0に近かった。私の相手をしてくれる企業はほとんどなかった。でも実務経験が2ヶ月を過ぎた頃からwantedly, green, folkwellでスカウトが来るようになった。4月に入社したばかりだが、6月に日本橋のスタートアップからオファーを頂いた。8月入社で1ヶ月働いた。緊急対応を裁きながらメインの難しいタスクを頑張ってやってる。すごく大変だけど、とても毎日が楽しい。

エンジニアになって悪かったこと

人と話す機会がほぼ無くなったことw

今働いているFin Techのスタートアップも、前働いていた受託開発も90%以上は1人でプログラミングをしている。今の職場はデイリースクラムがないので誰とも話さない日が多い。 悪かったこと、と書いているがこれは人によってはいいことになり得る。私も家に嫁と子ども2人がずっといて騒がしいので、寂しいということはない。

仕事が難しい

エンジニアの仕事は難しい。100%頭脳労働なので考えることが苦手な人にはきついと思う。エンジニアになる前の仕事では時間の切り売り感が強かったので8時間毎日座っていれば仕事は終わった。 だが今は違う。何時間かけてもコードが書けないと仕事をしていないことと同じである。質問してもすぐに返ってくるわけではないので高い自走力と根性が必要である。最近ちょっと疲れてきたので気晴らしにポエムを書いている。

悪かったことがほぼない

良かったことだけを書くと胡散臭いかな?と思い、上の二つは無理矢理作り出した。しかし自分にとってぶっちゃけ悪いことが一つもない。仕事が難しいのはちょっと大変だけど、難しいからこそ楽しい、みたいなことはある。 逆に簡単なバグ潰しとかやってても毎日がつまらない。「大きな仕事に取り組め、小さな仕事は己を小さくする」大きな仕事の方が圧倒的に楽しい。

結論:Web系エンジニア最高

  • 将来性
  • 働き方の柔軟性
  • 仕事の楽しさ
  • 毎日成長を実感できる喜び
  • 単価の高さ

などなど、エンジニアは本当にいい仕事だと思う。少なくとも自分は転職する前の幸福度を1だとすると今は300くらいある。だから1500hの勉強は非常にコスパのいい投資だった。 プログラミングが面白くなかったらできなかっただろうけど、幸い自分はプログラミングを楽しめるし技術も好きである。もちろん万人に向いている仕事ではないと思うが、向いている・向いていないを決めるのは自分である。他人から向いてないとか言われても気にする必要はない。世の中の大部分はノイズなのでいらないものをそぎ落とし、自分が信じる道を突き進むのみ!勇往邁進!