たばりばりスタイル

たばりばりスタイル

バリバリバリ⚡︎

フリーランスエンジニア(準委任)の直営業による案件獲得方法について

僕は現在参画している企業と仲介を挟まず直接準委任契約を結んでいます。

エージェント経由の経験しかない方とこの話をすると、どうやって案件を獲得したか必ずと言っていいほど聞かれます。 特に難しいことはしていないのですが、エージェント経由だと勝手に案件降ってくるので、イメージ湧かない気持ちはわかります。

今回は僕がどのように案件を獲得したかをまとめたいと思います。

直契約の案件獲得方法

まず、直契約で参画する方法には大きく 2 パターンあると思っています。

  • 人脈を使ってリファラルで参画
  • 全く人脈のない企業に直営業して参画

今回、僕は後者の直営業の方で参画していますので、こちらについてまとめたいと思います。

※ 実はありがたいことに、前参画先で一緒に働いていた方からリファラルの話もあったのですが、直営業を経験したいという理由からお断りさせていただきました。*1

全く人脈のない企業に直営業

直営業とだけ聞くと、営業スキルが必要な感じもしますが、全くそんなことはありません。

やることは転職に近い感じです。普通にエンジニアの求人を出している企業にコンタクトをとるだけです。

僕は Wantedly しか使っていないのですが、Wantedly には業務委託の求人が存在します。そこに応募し、準委任契約で参画したいことを伝えます。

普通に商談 (面接) して、希望単価を伝え、問題がなければオファーを貰い、契約に進む形です。契約時の書類周りは、基本的に企業側が用意してくれると思いますが、商談の際に確認しておくと安心かもしれません。

今回(2021/01~)から参画する企業を探している時は、3 件応募して 2 件オファーを貰いました。

今回の営業・商談結果

案件 技術スタック 商談結果 補足
HR テック系企業 Go/AWS オファー獲得 -> 成約 業務委託慣れしており、開発組織が大きい、Go 歴が長い
ヘルステック系企業 Go/GCP オファー獲得 -> お断り 技術的にもメンバー的にも良さそうな環境だが、将来的には正社員入社を望まれたため
ファッションテック系企業 Go/AWS オファー貰えず 少数精鋭な感じで良かったがオファー貰えず。採用枠は 1 名だが複数候補がいたよう

単価は隠していませんが、タダで公開だと勿体無いので省略。聞きたい人は直接聞いて貰えれば教えます。

直接契約だとマージンが発生しない分、貰える額が多くなると思うので、興味がある方はぜひ。

以上です。

*1:僕はキャリアも浅く、文系出身ということもあり、エンジニアの知り合いが少ないです。そのため、今後リファラルだけで食いつなぐことは厳しいと思っており、早いうちに直営業を経験したいと思っていました。

GORMで値オブジェクトチックな構造体を埋め込む

GORM には embedded というタグが用意されており、値オブジェクト(Value Object)チックな構造体を他の構造体に簡単に埋め込むことができそうなので、メモ。

gorm.io

値オブジェクト

値オブジェクトとは、住所情報のように、不変かつ交換可能なオブジェクトです。 "港区 六本木 1-1-x" という同じ値オブジェクトが 2 つ存在する場合、その 2 つは同等である必要があります。

今回は住所情報を持つ値オブジェクトチックな構造体を表現する Address Struct を用意し、他の構造体に埋め込む実装を行います。

"チック" と表現しているのは、今回の実装では値の再代入を禁止する方法がないため、完全な値オブジェクトと言えないと判断したためです。

Address Struct

住所情報を持つ Address Struct を用意する。Address Struct は Street (住所の通り情報) と City (住所の都市情報) を内部で持ち、それらを組み合せて使うメソッドが用意しています。

package valueobject

type Address struct {
    Street string
    City   string
}

// IsClose は引数で受ける住所情報と自身の住所情報を比較し、住所が近いかを真偽値で返す.
func (a *Address) IsClose(address Address) bool {
    return a.City == address.City
}

// ToString は住所情報を結合した文字列を返す.
func (a *Address) ToString() string {
    return a.Street + " " +  a.City
}

Customer Struct

実際に値オブジェクトを埋め込ませる構造体として Customer Struct を用意します。Customer Struct は Address (住所情報) を持つ構造体です。

ここで GORM の embedded タグを使って Address Struct を埋め込みます。

embeddedPrefix はカラム名プレフィックスを省略するのに使います。

package entity

import (
    vo "github.com/tabakazu/value_object_sample/valueobject"
)

type Customer struct {
    ID      uint       `gorm:"primaryKey"`
    Name    string     `gorm:"column:name"`
    Address vo.Address `gorm:"embedded;embeddedPrefix:address_"`
}

// これは下記と同等になる
// type Customer struct {
//     ID      uint       `gorm:"primaryKey"`
//     Name    string     `gorm:"column:name"`
//     AddressStreet string
//     AddressCity   string
// }

DB と連携

実際に実装した Struct と DB のデータをマッピングしてみます。

事前に下記のテーブル、データを用意します。

CREATE TABLE `customers` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COMMENT '名前',
  `address_street` varchar(255) COMMENT '住所 通り',
  `address_city` varchar(255) COMMENT '住所 都市',
  `created_at` datetime(6) NOT NULL,
  `updated_at` datetime(6) NOT NULL,
  `deleted_at` datetime(6) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `customers` (name,address_street,address_city,created_at,updated_at))
VALUES ("John Snow", "Roppongi 1-1-x", "Minato-ku", NOW(), NOW());

実際に DB からデータを取得します。埋め込んだ値オブジェクトも正しくマッピングされており、定義した各メソッドを呼び出せます。

//  DB からデータ取得
var cus entity.Customer
db, _ := gorm.Open(mysql.Open(os.Getenv("MYSQL_URL")), &gorm.Config{})
db.First(&cus, "name = ?", "John Snow")

// Address で定義された City を取得
cus.Address.City //=> "Minato-ku"

// Address で実装した IsClose メソッド
otherAddress := valueobject.Address{City: "Minato-ku"}
cus.Address.IsClose(otherAddress) // => true
otherAddress := valueobject.Address{City: "Shibuya-ku"}
cus.Address.IsClose(otherAddress) // => false

// Address で実装した ToString メソッド
cus.Address.ToString() // Roppongi 1-1-1 Minato-ku

// Address の比較
otherAddress := vo.Address{City: "Minato-ku", Street: "Roppongi 1-1-x"}
cus.Address == otherAddress // true
otherAddress := vo.Address{City: "Minato-ku", Street: "Roppongi 1-1-y"}
cus.Address == otherAddress // false

データ挿入時も問題なく動作します。

cus := entity.Customer{
    Username: "Tyrion Lannister",
    Address: vo.Address{
        City:   "Minato-ku",
        Street: "Roppongi 1-1-x",
    },
}

db, _ := gorm.Open(mysql.Open(os.Getenv("MYSQL_URL")), &gorm.Config{})
if err := db.Create(&cus).Error; err != nil {
    fmt.Println(err)
} else {
    fmt.Println("Success")
}
// Success

良き値オブジェクトを発見した時は是非使いたい機能でした。

以上です。

2020年の振り返り。無事コロナ禍を生き残りました

コロナ禍を無事生き残れたので、2020 年の振り返りを書きます。

2020 年の振り返り

どんなことをやっていたのか

3 月 - ヘルステック系 Web ベンチャー退職

1 年ほど勤めていたヘルステック系 Web ベンチャーを退職しました。 新規開発プロジェクトで、設計からリリースまで経験できたのですごく良い経験になりました。

参画していたプロジェクトも落ち着き、組織編成などでチームリソースも充実していたので、タイミングが良さそうと判断し、退職しました。

4 月 - 独立開始 & ビジネス系試験サービスのシステム開発に業務委託として初参画、契約終了

開業届を提出し、エージェント経由で紹介してもらった企業に参画しました。技術がモダンで、サービスが面白かったので参画を決めました。

独立後初参画だったためやる気満々でしたが、参画から数日後に緊急事態宣言が発令され、予算縮小による業務委託の一斉終了で 1 ヶ月での契約終了となりました。幸先悪い感じでの独立になりました。

ただ期間は少なかったですが、SendGrid や CloudScheduler(GCP) などの未経験 SaaS を経験できたことや、1 つの新機能(詫び石的な機能)を設計から実装まで行えたので、楽しかったです。

次のタスクとして GraphQL を利用した API 実装にアサインされていたので、本音を言えばもう少し参画したかった...。

5 月 - ヘルステック系サービスのシステム開発に業務委託として参画

GW 明けからは、再度エージェント経由で紹介してもらい、別企業に参画しました。この参画先には、新規開発フェーズから参画し、各種主要機能、認証機能、決済機能、Slack 通知、GAS や AWS との連携部分の対応を行いました。

初めてスクラムマスターのいるチームで本格的な?スクラム開発が経験できて楽しい部分がある一方で、学習コストの問題で、SPA 化しない、TS は利用しない、状態管理に Redux を使わない、functional component ではなく class ベースで記述するなど、んーと思う部分もあり、サービスリリースで初期開発が落ち着いたタイミングの 12 月末で契約終了とさせていただきました。

11 月 - 次の参画先探しの営業を開始

独立後からエージェント経由で参画先を探していたのですが、単価交渉などでエージェントを仲介しなければいけない、案件探しと契約書周りの調整以外でエージェントを利用するメリットを感じない、などの理由から個人で営業する形を開始しました。

これまでのキャリアではサーバサイド開発で Rails しか使ったことしかなく、動的型付け言語 + 高機能フレームワークの開発経験しかないことがコンプレックスだったため、静的型付け言語 + 薄いフレームワークで開発できる Go の案件を中心に探しました。

12 月 - エージェント経由の参画終了、次の参画先決定

11 月から営業を開始、3 件営業して 2 つのオファーをもらうことができました。 どちらも Go でサーバサイド開発を行う案件です。どちらも魅力的な参画先でしたが、業務委託として参画する上で働きやすそうな環境かどうかを基準に選びました。

これまで Go 言語の業務経験はありませんでしたが、サーバサイドの開発経験、TDD やスクラム開発の経験、クラウド連携の経験など、比較的モダン寄りな技術スタックがあれば、話は聞いてもらえる感じでした。

単価については、これまでの単価にエージェントのマージン分 (推定) を足した金額で交渉。問題なく契約できました。

エージェント経由だと発生しない書類のやり取りなどで時間が取られましたが、色々勉強になりました。

まとめ

意気揚々と独立して、その 1 ヶ月後にコロナにぶつかり怪しいところもありましたが、しっかり生き残ることができ、自信につながりました。 来年からは Rails から離れ、未経験な言語にチャレンジすることになりますが、しっかりキャッチアップし、参画先に貢献できるように頑張りたいと思います。

だいたいこんな感じの 2020 年でした!こんな僕でも 2020 年を乗り越えれました。 来年も 1 月から新しい環境にチャレンジしていく形なので、引き続き生き残れるように頑張ります。

個人的によく使うGitコマンドのユースケースごとの備忘録

普段使い PC 上だと history を参照してある程度必要な操作はできるのですが、新しい支給 PC を利用するシーンなどでは Git コマンドを忘れてググることが多かったので、備忘録として残しておきます。

ちなみに直近でよく使った Git コマンドは下記です。

$ history | grep '^git ' | awk '{ print $1, $2 }' | sort | uniq -c | sort -r | head
1619 git commit
 820 git add
 535 git checkout
 281 git diff
 219 git push
  81 git reset
  78 git log
  74 git clone
  72 git branch
  70 git status
  57 git remote
  25 git rebase
  19 git pull
  18 git cherry-pick
  17 git merge
  15 git show
  10 git config
   9 git stash
   5 git revert
   4 git secrets

git commit でよく使うケース

直前の commit にまとめたいケース

$ git add README.md
$ git commit --amend --no-edit

直前のコミットのメッセージを編集したいケース

$ git commit --amend

変更部分を表示してコミットしたいケース

$ git commit -v

git add でよく使うケース

特記事項なし

git checkout でよく使うケース

checkout は peco と組み合わせた alias を使って下記コマンドを使っています。かなり便利です。

alias gc='git branch | peco | xargs git checkout'

リモートのブランチに checkout したいケース

$ git pull
$ git checkout "target_remote_branch_name"

git diff でよく使うケース

別ブランチとの差分を確認したいケース

$ git diff "target_branch_name" --name-only # ファイル名のみ
$ git diff "target_branch_name" README.md # 特定ファイルの差分のみ

git push でよく使うケース

$ git push origin HEAD
$ git push origin HEAD -f # force

git reset でよく使うケース

各種取り消ししたいケース

$ git reset # add 取り消し
$ git reset HEAD^ # 直前のコミットを取り消し
$ git reset --hard # add 取り消し & 現在の変更内容を破棄

git log でよく使うケース

$ git log --oneline README.md | head # ログを 1 行表示

git clone でよく使うケース

特記事項なし

git branch でよく使うケース

不要ブランチを削除したいケース

$ git branch -D "target_branch_name"

git status でよく使うケース

特記事項なし

git remote でよく使うケース

既存リモート URL 一覧を表示したいケース

$ git remote -v

git rebase でよく使うケース

複数コミットをまとめたいケース

$ git rebase -i HEAD~3 
# $ git rebase --abort # conflict した場合に利用

git pull でよく使うケース

リモートで更新されたブランチを pull したいケース

<master> $ git checkout "develop" # 対象ブランチに checkout
<develop> $ git pull origin "develop" # ブランチ名を指定して pull

git cherry-pick でよく使うケース

普通に cherry-pick

<master> $ git checkout "target_branch"
<target_branch> $ git cherry-pick a1b2c3d4

git merge でよく使うケース

feature/123 ブランチに develop ブランチをマージしたいケース (feature/123 <- develop)

<master> $ git checkout feature/123
<feature/123> $ git merge develop

git show でよく使うケース

特記事項なし

git config でよく使うケース

user 設定したいケース

$ git config --global user.name tabakazu
$ git config --global user.email tabakazu@example.com

git stash でよく使うケース

変更を一時退避して、戻したいケース

$ git stash save # 変更を一時退避
$ git stash list # 退避した変更の一覧
$ git stash pop "stash@{0}" # 退避した変更の取り出し

git revert でよく使うケース

普通に revert

$ git revert a1b2c3d4

git secrets でよく使うケース

クラウド破産しないように git-secrets を使う - Qiita

その他

fork 先を追従したいケース

GitHubでFork/cloneしたリポジトリを本家リポジトリに追従する - Qiita

$ git remote add upstream https://github.com/rails/rails.git
$ git remote add upstream git@github.com:rails/rails.git
$ git checkout master
$ git fetch upstream
$ git merge upstream/master

コミット数ランキングを表示したいケース

git shortlog -s -n

以上です。

友人へ贈るWebエンジニア転職の学習ならこうすればいいんじゃね的な順番

最近、Webエンジニアになりたいという友人(未経験)から、転職するためのプログラミングの学習順について尋ねられた。

Web上に公開されているいくつかのロードマップから良さそうなものを共有してみたが、僕なら実際こうするかなっていうのがあったのでひとつのケースとして残したいと思います。 ※ 僕は普段 Rails を使っているので、Rails を使う前提に

僕について

  • 現在は業務委託で Web エンジニアをしている
  • SIer 2 年、Web 系 2 年弱の経験あり
    • 未経験(文系大学生)から SIer 入社、SIer から Web 系に転職を経験
  • 現在の主な利用技術は Rails / AWS / React など
  • Rails を初めて触ったのは 4.2、初めて業務で利用したの 5.1

僕のおすすめしたいロードマップについて

極力、最短で Ruby on Rails チュートリアルを始めた方がいいと思っています。

Ruby on Rails チュートリアルは本当に素晴らしい教材だと思っており、チュートリアル内で Ruby、HTML/CSS/JavaScript はもちろん、UNIX コマンドや Git に触れることができます。

チュートリアルを始める前に、Ruby、HTML/CSS/JavaScriptUNIX コマンド操作、Git を学ぶことを勧めている記事もありますが、それらは Rails チュートリアル中に適宜学ぶ形でも良いと思っています。*1

その上で、下記の流れを勧めます。*2

  1. HTML/CSS/JavaScript 基礎
  2. Ruby on Rails チュートリアル
  3. Ruby on Rails を使って自作アプリを作る

HTML/CSS/JavaScript 基礎

Progate やドットインストールでコースを 1 周。

HTML/CSS/JavaScript が何をするものか、ある程度理解できていれば良さそう。

dotinstall.com

dotinstall.com

dotinstall.com

Ruby on Rails チュートリアル

railstutorial.jp

前提知識はそろっていないので挫折率は高そう *3ですが、ひとまず 1 周することを目標に進める。 Ruby や HTML/CSS/JavaScript の書き方、UNIX コマンドの意味など、わからないことがあれば都度調べてください。

ただ、ここで意識して欲しいのはチュートリアルの内容を 100% 理解しようとしないでほしいことです。50% の理解でも上出来と考えるといいと思う。*4

1 周した後は都度調べた内容を復習して、2 周目に入ることをオススメしたい。 人によっては 1 周でいいという人もいるが、個人的には 2 周した方が気付きが多いと思う。一度通った道なので余裕を持って進められ、これまでの曖昧な知識の断片同士が繋がって、1 周目で見えなかったところも見えてくると思う。

2 周目でも意識して欲しいのは、ここでも完全に理解していなくても問題ないということ。*5

Ruby on Rails を使って自作アプリを作る

チュートリアル後は自作のアプリを作ることをお勧めします。これは、完成コードが用意されていた教材ではなく、自分で考えて作ることによって Rails と Web 開発に対して、より深い理解を得ることができるからです。

もし、可能ならチュートリアルで出てこないライブラリを使うのもありです。個人的には現場でよく使われる認証系ライブラリの Devise は使っておくと良いかなと思います。

その後余裕があったら学ぶことをおすすめしたいもの

現場に出るとチュートリアルの知識だけでは足りない知識もあります。それを補うために下記を学ぶと良いと思います。

  • テストコード (RSpec)
  • フロントエンド開発 (React.js / Vue.js etc)
  • インフラ構築 (AWS / GCP)

もし、学んだ上記を自作アプリに組み込めるとさらに良いと思います。*6

おわりに

いえ〜い、○○見てる〜? *7

*1:先に学んでも実際に必要にならないと覚えれないことが多いため

*2:僕が Rails をはじめて触った時もこんな感じでした

*3:挫折しても起き上がればいい!!!w

*4:Ruby on Rails チュートリアルは難易度はふつうに高い

*5:そもそもチュートリアルの内容を完全に理解することがゴールではない。Railsに慣れることがゴール

*6:実際に使ってみたほうが覚える

*7:分からないことがあれば都度聞いて〜!

半年フリーランスをやった雑感

2020/04 からフリーランスを始め、半年経った現状の僕の雑感についてまとめます。

はじめたきっかけ

正社員時代の給料に満足できなかったことがフリーランスになったきっかけです。

上司が他メンバーより多めに賞与をくれても、次回の査定で昇給させると言われても、満足できず、転職しても状況が変わらなかったので、自分で単価を指定できるフリーランスになりました。

正社員時代は、所属したチームに業務委託の方がいることも多く、技術レベル感を把握していたため、このレベルなら自分でもイケそうと、スキル面での不安は特にありませんでした。

順番としては、SIer -> Web 系ベンチャー -> フリーランス という現在の王道スタイルだと思います。

業務内容

エージェントを通して契約した企業に準委任契約で参画するスタイルです。

普通に Rails や React を使って開発したり、AWS を触ったり、コードレビューしたり、MTG したり、正社員と特に変わりません。

正社員自体と比べて業務はあまり変わりませんが、金額がよくなった分、責任が重く感じるようになりました。

単価について

レバテックさんの単価診断通りくらいの単価で働いています。※ エージェントを通して

プログラマ・SE(システムエンジニア)向けの単価・単金診断テスト
https://freelance.levtech.jp/service/assess/

ちょくちょく Wantedly や他求人媒体でオファーを頂くので、どこかのタイミングで直契約にもっていくのも有りかなと思っています。

フリーランスをして感じたこと

よくも悪くも作業に没頭できるなと感じます。

例えば、正社員に限定される MTG やイベントに参加せず、タスクを淡々とこなすことができます。

疎外感を感じることもありますが、"とりあえずコードを書きたい"って人にはすごくいい環境だと思います。

また、ベースとなる稼働時間があるため、基本的に残業はなく、私生活も安定しやすいと思います。もしリリース前など稼働が多くなっても、オーバーした分の額をいただけたので満足です。(単価が高いので正社員時代と比べ物にならない残業代です。)

今のところフリーランスになってからメリットしかないので、1年経過した時点で同じようなまとめを書こうと思います。 その頃は確定申告も経験するので考えが変わるかも?です。

以上です。

RSpecのCustom Matcherをカジュアルに追加している

Rails で開発、テスト (RSpec) していると、Custom Matcher を作りたくなるケースがあります。

例えば下記のようなケースです。

RSpec.describe User, type: :model do
  context 'Userのemailがnilの場合' do
    subject { build :user, email: nil }
    before { subject.valid? }

    # Userのemailがnilの場合にemailのエラーを持つか確認
    it { expect(user.errors.has_key?(:name)).to eq true } # ワンライナー
  end
end
# テストを実行
$ bundle exec rspec -fd
User
  Userのemailがnilの場合
    is expected to eq true

このテストだと出力でテスト内容が理解できませんし、テストの内容と出力にズレが生じています。(User の email が nil の場合に true が期待されているように見える

個人的にこの出力のされ方が好きではないため、expect の引数であれこれ記述しないようにしています。

このようなケースでは下記のように Custom Matcher を自作してテストをしています。

# Custom Matcher
RSpec::Matchers.define :have_error do |expected|
  match do |actual|
    actual.errors.has_key? expected
  end
  description do
    "have an error in #{expected}"
  end
end

RSpec.describe User, type: :model do
  context 'Userのemailがnilの場合' do
    subject { build :user, email: nil }
    before { subject.valid? }

    it { is_expected.to have_error :email }
  end
end
# テストを実行
$ bundle exec rspec -fd
User
  Userのemailがnilの場合
    is expected to have an error in email

上記の出力のように Custom Matcher を使うだけで、テスト内容が出力からある程度理解でき、テストの内容と出力で起こるズレを減らせます。

なので、既存の Matcher で it { is_expected.to matcher_name expected } のように書けないテストには、カジュアルに Custom Matcher の追加を検討していくようにしています。

ただ、これまでの現場ではあまり Custom Matcher を使っているところがなかったので、実際どうなんだろう。

以上です。