ryota21silvaの技術ブログ

Funna(ふんな)の技術ブログ

エンジニア1年生による備忘録。未来の自分が救われることを信じて

【記録】良記事とかをまとめておく

マインド

質問・学習などについて

各社研修

検索系

キャリア

効率系

英語

個人開発

デザイン

Mac

Linux

Vim

Git

GitHubのREADMEをサクッと高品質で書けるサービス作ってみました。 - Qiita

HTTP

ネットワーク

データベース

Ruby

Rails

テスト

RSpec

エラー、デバッグ

API

おもろい

【TypeScript】Non-null assertion operator(変数の末尾に!)

  • 「この変数はnullやundefinedeではないよ〜」とコンパイラに教えてくれる。つまり、 <T | undefined | null> 型の変数xがあったとして、x! って書いたらnull および undefinedを除外した<T>を生成してくれる。
  • 実行時(JSに変換された後)にこのコードは削除される。
    • !オペレーターはあくまで型チェックをスキップするだけで、実行時に null などが入る可能性は依然としてあることに注意。
      • なのでjestを書く場合などは expect(...).not.tuBeNull() などでチェックしてあげる必要がある。

www.typescriptlang.org

【Go】Enumを扱う

Enum(列挙型)とは

列挙型とは、プログラミング言語やデータベース管理システムなどにおけるデータ型の一つで、複数の異なる定数を一つの集合として定義するもの。多くの言語では “enum” の略号で示される。 列挙型(集合型)とは - IT用語辞典 e-Words

とあるように、複数の定数を1つのクラス(型)としてまとめて管理できるもの。

Javaであれば

public enum Fruits{
    ORANGE,
    APPLE,
    BANANA
};

のように書けるみたい。 引数や戻り値の型をEnumに限定できるのでプログラムの堅牢性が増すし、可読性が上がったりする。

qiita.com

www.modis.co.jp

GoでEnumを扱うには?

GoにはJavaにおけるEnum(列挙型)のような機能が無い。
そのため、const定義とその中に iota (※1)を使うことでEnumを扱うのが一般的?かもしれない。

ただし、Goは変数を初期化する際に明示的に値を代入しない場合に、デフォルトで割り振られる値が決まっているので注意。
これをZero Value(ゼロ値)(※2)といい、int型の場合は0で、string型の場合は“”で初期化されるようになっている。

※1: iotaとは定数宣言(const)内で使用される識別子のことで、0から始まる型なしの整数連番を生成してくれる。
※2: Zero Value(ゼロ値)については、ゼロ値を使おう #golang - Qiitaが分かりやすい。



この仕様から、Goでは以下のように定数の割り当てを1からにすればいい。

type Fruit int

const (
    ORANGE Fruit = iota + 1 // 1
    APPLE                   // 2
    BANANA                  // 3
)

以下のfruits := Fruits{}は明示的に値を代入していないので、Zero Valueの仕様により、fruits.Fにはint型のデフォルト値の0が入ってしまう。

そのため iota + 1としておけば、意図した挙動になる。

type Fruits struct {
    F Fruit
}

func main() {
    fruits := Fruits{} // 変数初期化時に値を代入していないので、0が割り振られる

    switch fruits.F {
    case ORANGE:
        fmt.Println(“オレンジ”) // (+1していないと、この分岐を通ってしまう)
    case APPLE:
        fmt.Println(“りんご”)
    case BANANA:
        fmt.Println(“バナナ”)
    default:
        fmt.Println(“エラー”) // この分岐を通ってくれる
    }
}

参考記事

【Go】JSONの入れ子を構造体に変換する

■ はじめに

APIから返ってくるJSON文字列をGolangの構造体に変換したい。

今回はGoogle Books APIを題材にしてみます。

Google Books APIについてはhttps://www.googleapis.com/books/v1/volumes?q=検索したい書籍名で叩くことができます。

f:id:ryota21silva:20220213181237p:plain

JSONを構造体に変換する便利ツール

JSONを構造体の形式にマッピングするためには、json-to-goを使うと便利です。

f:id:ryota21silva:20220213180854p:plain
json-to-go

JSONを構造体に変換する基本形

Google Books APIのうち、以下部分だけ抽出して構造体にマッピングしたい場合、

{
    "book":{
        "google_books_id": "Wx1dLwEACAAJ",
        "title": "リーダブルコード",
        "authors": ["Dustin Boswell","Trevor Foucher"],
        "description": "読んでわかるコードの重要性と方法について解説",
        "isbn_10": "4873115655",
        "isbn_13": "9784873115658",
        "page_count": 237,
        "published_year": 2012,
        "published_month": 6
    }
}

以下のように記述すればOKです。

type BookRequestParameter struct {
    GoogleBooksId string    `json:"google_books_id"`
    Title         string    `json:"title"`
        Authors   []string      `json:"authors"`
    Description   string    `json:"description"`
    Isbn_10       string    `json:"isbn_10"`
    Isbn_13       string    `json:"isbn_13"`
    PageCount     int       `json:"page_count"`
    PublishedYear   int     `json:"published_year"`
    PublishedMonth   int    `json:"published_month"`
}

JSON入れ子を構造体に変換する

GolangAPIサーバーとして立てたときに、JSONのリクエストボディを構造体に変換したいケースもあります。

例えば以下のようなJSON入れ子を構造体にマッピングする場合、

{
    "book":{
        "google_books_id": "Wx1dLwEACAAJ",
        "title": "リーダブルコード",
        "authors": ["Dustin Boswell","Trevor Foucher"],
        "description": "読んでわかるコードの重要性と方法について解説",
        "isbn_10": "4873115655",
        "isbn_13": "9784873115658",
        "page_count": 237,
        "published_year": 2012,
        "published_month": 6
    },
    "memo": {
        "body": "メモです。"
    }
}

以下のどちらかの方法で、構造体をフィールドに持つ構造体を定義してあげると良いです。

type RegisterUserBookRequestParameter struct {
    Book BookRequestParameter `json:"book"`
    Memo MemoRequestParameter `json:"memo"`
}

type BookRequestParameter struct {
    GoogleBooksId string    `json:"google_books_id"`
    Title         string    `json:"title"`
    Description   string    `json:"description"`
    Isbn_10       string    `json:"isbn_10"`
    Isbn_13       string    `json:"isbn_13"`
    PageCount     int       `json:"page_count"`
    PublishedYear   int  `json:"published_year"`
    PublishedMonth   int     `json:"published_month"`
    PublishedDate   int  `json:"published_date"`
}

type MemoRequestParameter struct {
    Body          string    `json:"body"`
}
type RegisterUserBookRequestParameterr struct {
    Book struct  {
        GoogleBooksId string    `json:"google_books_id"`
        Title         string    `json:"title"`
        Description   string    `json:"description"`
        Isbn_10       string    `json:"isbn_10"`
        Isbn_13       string    `json:"isbn_13"`
        PageCount     int       `json:"page_count"`
        PublishedYear   int  `json:"published_year"`
        PublishedMonth   int     `json:"published_month"`
        PublishedDate   int  `json:"published_date"`
    } `json:"book"`
    Memo struct {
        Body          string    `json:"body"`
    }`json:"user_book"`
}

【Git】git rebase -iでpush済みのコミットまとめたれ

コミットをまとめる

コミットBとCをまとめたい

$ git log -n 3                                                                                                                                                              
commit 78dbdc4439854253a4c65a23aad687488bcf2051 (HEAD -> feature/hogehoge)
Author: ryota1116
Date:   Tue Sep 21 01:27:14 2021 +0900

    コミットC

commit 185f507a6a32fd93505db1cd05b6effa23f45532
Author: ryota1116
Date:   Tue Sep 21 01:14:21 2021 +0900

    コミットB

commit f19845709c26dea827c6097279472ecc3ddda491
Author: ryota1116
Date:   Tue Sep 21 01:10:07 2021 +0900

    コミットA

git rebase -iでコミットをまとめる。

$ git rebase -i HEAD~3

pick b536dec90 コミットA
pick 7fc1e2c81 コミットB
pick 3c98d9014 コミットC

# Rebase 185f507a6..3c98d9014 onto 185f507a6 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

コミットC(3c98d9014)を、1つ前のコミットB(7fc1e2c81)にまとめたい場合以下のように書き換える。 squash(押し潰す)されたコミットCをコミットBが拾う(pick)。

$ git rebase -i HEAD~3

pick b536dec90 コミットA
pick 7fc1e2c81 コミットB
s 3c98d9014 コミットC

:wqでファイルを保存しviを終了すると、以下の画面になる。

# This is a combination of 2 commits.
# This is the 1st commit message:

コミットB

# This is the commit message #2:

コミットC

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Sep 24 16:49:38 2021 +0900
#
# interactive rebase in progress; onto 185f507a6
# Last commands done (3 commands done):
#    pick 7fc1e2c81 コミットB
#    squash 3c98d9014 コミットC
# No commands remaining.
# You are currently rebasing branch 'feature/hogehoge
#
# Changes to be committed:
#       modified:   src/hogehoge.php
#       modified:   src/fuga.php
#       modified:   src/piyo.php
#

コミットメッセージをまとめてあげて、:wq

# This is a combination of 2 commits.
# This is the 1st commit message:

コミットB+

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Fri Sep 24 16:49:38 2021 +0900
#
# interactive rebase in progress; onto 185f507a6
# Last commands done (3 commands done):
#    pick 7fc1e2c81 コミットB
#    squash 3c98d9014 コミットC
# No commands remaining.
# You are currently rebasing branch 'feature/hogehoge
#
# Changes to be committed:
#       modified:   src/hogehoge.php
#       modified:   src/fuga.php
#       modified:   src/piyo.php
#

以下の画面に戻る。

$ git rebase -i HEAD~3
[detached HEAD 51ffdbb8d] コミットB+
 Date: Fri Sep 24 16:49:38 2021 +0900
 3 files changed, 11 insertions(+), 3 deletions(-)
Successfully rebased and updated refs/heads/feature/hogehoge

コミットが纏まったことが分かる。

$ git log -n 3                                                                                                                                                              
commit 51ffdbb8dc3f2b5336f5394739599313c25022a0 (HEAD -> feature/hogehoge)
Author: ryota1116
Date:   Fri Sep 24 16:49:38 2021 +0900

    コミットB+

commit b536dec90d6505bcfc6ae2f3f74ba03a5eeca8b3
Author: ryota1116
Date:   Tue Sep 21 01:27:14 2021 +0900

    コミットA

commit 185f507a6a32fd93505db1cd05b6effa23f45532
Author: ryota1116
Date:   Tue Sep 21 01:14:21 2021 +0900

    コミットα

問題無ければforce push。 同じブランチで他の人が一緒に作業してたりするなら、force pushは避けましょうね。

$ git push -f

まとめたコミットを戻したい時

git logではsquash後のコミットしか見れないが、

$ git log -n 3                                                                                                                                                              
commit 51ffdbb8dc3f2b5336f5394739599313c25022a0 (HEAD -> feature/hogehoge)
Author: ryota1116
Date:   Fri Sep 24 16:49:38 2021 +0900

    コミットB+

commit b536dec90d6505bcfc6ae2f3f74ba03a5eeca8b3
Author: ryota1116
Date:   Tue Sep 21 01:27:14 2021 +0900

    コミットA

commit 185f507a6a32fd93505db1cd05b6effa23f45532
Author: ryota1116
Date:   Tue Sep 21 01:14:21 2021 +0900

    コミットα

git reflogでHEADの移動履歴を確認できる。
3c98d9014まで戻せば良さそう。

$ git reflog -n 11
78dbdc443 (HEAD -> feature/hogehoge) HEAD@{0}: rebase -i (finish): returning to refs/heads/feature/hogehoge
78dbdc443 (HEAD -> feature/hogehoge) HEAD@{1}: rebase -i (squash): コミットB+
549583fe1 HEAD@{2}: rebase -i (squash): # This is a combination of 2 commits.
b536dec90 HEAD@{3}: rebase -i (start): checkout HEAD~3
3c98d9014 HEAD@{4}: rebase -i (abort): updating HEAD
3c98d9014 HEAD@{5}: rebase -i (abort): updating HEAD
bdd5515b2 HEAD@{6}: rebase -i (squash): # This is a combination of 2 commits.
b536dec90 HEAD@{7}: rebase -i (start): checkout HEAD~3
3c98d9014 HEAD@{8}: commit: コミットC
7fc1e2c81 HEAD@{9}: commit: コミットB

git reset --hard で戻す。

$ git reset --hard 3c98d9014                                                                                                                                           
HEAD is now at 3c98d9014 コミットC

reflog見ると、HEADの位置が移動していることが分かる。

$ git reflog -n 12
3c98d9014 (HEAD -> feature/hogehoge) HEAD@{0}: reset: moving to 3c98d9014
78dbdc443 HEAD@{1}: rebase -i (finish): returning to refs/heads/feature/hogehoge
78dbdc443 HEAD@{2}: rebase -i (squash): コミットB+
549583fe1 HEAD@{2}: rebase -i (squash): # This is a combination of 2 commits.
b536dec90 HEAD@{3}: rebase -i (start): checkout HEAD~3
3c98d9014 HEAD@{4}: rebase -i (abort): updating HEAD
3c98d9014 HEAD@{5}: rebase -i (abort): updating HEAD
bdd5515b2 HEAD@{6}: rebase -i (squash): # This is a combination of 2 commits.
b536dec90 HEAD@{7}: rebase -i (start): checkout HEAD~3
3c98d9014 HEAD@{8}: commit: コミットC
7fc1e2c81 HEAD@{9}: commit: コミットB

参考記事

Railsの利点と欠点、RailsがDDDや大規模開発に向いていない理由

知り合いが「Railsの長所ってなんやっけ?」って話をしてたので、その辺のRailsの長所・短所とか、RailsがDDDや大規模開発に向いてない理由を自分なりに整理してみた。

間違っている点などあれば是非ご指摘頂きたいです。

Rails(Active Record)の利点と欠点

RailsというかActive Recordの利点と欠点を話しているだけ。

  • Railsの利点
    1. RDBのテーブルとActive Recordのモデルが一対一になり直感的でわかりやすい。CRUD機能、バリデーション、ロジックなどをすべてActive Recordモデルに書けるので短期的には爆速で開発できる。
    2. これはORM全般に共通する利点なので蛇足ですが、オブジェクト(モデル)を直感的に操作するだけでDBアクセスする処理を書ける。生SQLを直接書かずに済む(どんなSQLが発行されているかは確認しないとですが)

 つまりViewやControllerなどあらゆるコードがActiveRecordと密結合になるので、例えばViewに渡っているデータの表示方法を変更したいときはActiveRecordモデルのコードをイジるだけで済んじゃったり。 短期的な開発効率が上がるので爆速でMVPをリリースできるし、スタートアップの開発に向いている。

  • Railsの欠点
    1. テーブルとActiveRecordモデルが一対一の関係にあるため、例えばUserに関するデータ・振る舞い(CRUD操作も含む)・制約などがActiveRecordのUserモデルに集約されがち。
      => Userというモデルは用途やユースケースに応じて持つべきデータ・振る舞い・制約が異なる可能性があるのに、ActiveRecordのUserクラスにあらゆるユースケースの処理を生やしていくと1つ1つのメソッドがどのユースケースで使われるべきか分かりづらくなる。例えばnameプロパティにバリデーションをかける場合もユースケースによって制約内容が異なるかもしれない。
      ActiveRecordは特定の1つのユースケースに特化したコードを書くことには向いているが、複数のユースケースに対応するコードを書く場合(テーブルとモデルが一対一とならない場合)に苦労する。

    2. Active RecordモデルがControllerやViewに渡りやすいことから、あらゆるレイヤーがActiveRecordと密結合な状態になってしまう。
      => 1とつながる内容ですが、アプリケーションが複数のユースケースを持ち始めた場合に問題が生じてくる。複数のユースケースと密結合なActiveRecordモデルのオブジェクトがあらゆるユースケースのViewやControllerに渡ってしまうと「ユースケースAではどのデータ(プロパティ)、どのメソッドを用いるのか分からない、、」ってなるし、変更漏れが発生しそう。
      そして複数のユースケースのロジックを1つのActiveRecordモデルに記述すると可読性が落ちるし責務の大きい肥大モデルが生まれてしまう。

つまり、RailsActiveRecordには

などの処理が集約されているため、複数のユースケースに考慮したコードが書き辛い

この辺りはRuby on Railsの正体と向き合い方 / What is Ruby on Rails and how to deal with it? - Speaker Deckがめっちゃ分かりやすかったです。
以下に一部引用文を抜粋。

Railsはいつ、なぜ限界を迎えるのか?
・いつ:あるModelが複数の異なるユースケースでC(R)UD操作されるようになったとき。
・なぜ:あるModelに書かれたValidations/Callbacksは特定のユースケースと密結合しているため。
・何が辛いのか:あるユースケースに特化したModelの中で、別のユースケースの事情を考慮したコードを書かなければならないこと。


じゃあRailsで複数のユースケースにどうやって対応するの?

RailsやLaravelで複数のユースケースに対応した長期的な開発を取り組むためにDDDを実践したい場合は、Active Recordモデル(Eloquentモデル)をそのままViewやControllerに返すのではなくユースケースに沿ったドメインオブジェクトを返してあげるのが良い。
そしてInfra層のリポジトリ実装クラスでは、Active Record(Eloquent)のORMのみを参照する(DDDではDomain層のRepositoryでリポジトリインターフェイスを定義し、Infrastructure層で永続化の処理を行うので)。
そして他のどのレイヤーからもActiveRecordの機能を参照しないのが良さそう?参照するならインターフェイス噛まして隠蔽するのが良いんかな。(Railsインターフェイスってどんなやったっけ)


おわりに

そもそもRailsの長所は短期的に爆速で開発できることであって、大規模開発には向いてないみたい。
でもRailsライクで生まれたCakePHPはCakeのエンティティ(DDDのエンティティとは別)と密結合になりがちやし、LaravelもEloquentモデルと密結合になりやすいから、Railsに限らん話では?と思った。

その他参考資料

PHPでポリモーフィズム、インターフェイスを理解する

ポリモーフィズム多態性)とは

ポリモーフィズムとは「同名のメソッドを異なるクラス間で使用できるようにすること」を意味しています。

例えば以下のようなSoldierクラス、Wizardクラス、Warriorクラスが存在し、これらのクラスに「攻撃をするメソッド」を定義したとします。

(※この例は以下の記事を参照しています。)

<?php
class Soldier {
    private const SOLDIER_POWER = 2;
    
  // 攻撃をするメソッド
    public function slash(): int {
        return self::SOLDIER_POWER;
    }
}
<?php
class Wizard {
    private const WIZARD_POWER = 1;
    
  // 攻撃をするメソッド
    public function cast(): int {
        return self::WIZARD_POWER;
    }
}
<?php
class Warrior {
    private const WARRIOR_POWER = 3;
    
  // 攻撃をするメソッド   
    public function knuckle(): int {
        return self::WARRIOR_POWER;
    }
}

これらの各メソッドは「攻撃をする」という同じ目的を持っていますが、クラスごとに別名で定義されています。

そこでポリモーフィズムを用いてみます。
まずCharacterというスーパークラスを作成し、Characterクラスを継承する複数のサブクラスではattack()という同名のメソッドを定義してあげます。

(Character.php)

<?php
class Character {
    // この記述はポリモーフィズムとしては不完全である(後述)
    public function attack(): int {
        return 0;
    }
}
<?php
require_once 'Character.php'

class Soldier {
    private const SOLDIER_POWER = 2;
        
    public function atack(): int {
        return self::SOLDIER_POWER;
    }
}
<?php
require_once 'Character.php'

class Wizard {
    private const WIZARD_POWER = 1;
        
    public function atack(): int {
        return self::WIZARD_POWER;
    }
}
<?php
require_once 'Character.php'

class Warrior {
    private const WARRIOR_POWER = 3;
        
    public function atack(): int {
        return self::WARRIOR_POWER;
    }
}

このようにポリモーフィズム同名のメソッドで異なる挙動を実現してくれるため、同じ目的を持った機能を呼ぶために各クラスにわざわざ異なる名前のメソッドを定義する必要が無くなります。

これによってコードの書き直しが減る&クラスごとにメソッド名を覚える必要が無くなるため、記述の間違いが減るなどのメリットが得られます。大規模な開発を行う場合は特に恩恵が受けられそうです。

抽象クラス/抽象メソッド

ただし、上記の定義では「サブクラスがattack()メソッドを実装してくれることが保証されていない」ため、ポリモーフィズムとしては不完全です。

スーパークラスとしてはサブクラスがattack()メソッドを実装してくれることを期待していたとしても、サブクラスはattack()メソッドと同じ目的のメソッドを別に定義してしまう可能性があります。サブクラスがスーパークラス側の都合を理解しているとは限りません。

そこで使用するのが抽象メソッドです。
抽象メソッドとは「それ自身が機能を持たない空のメソッドのこと」です。

抽象メソッド自体は機能を持たないため、外部(サブクラス)から機能を与えてあげる必要があります。つまり抽象メソッドとは「サブクラスでオーバオライドされることを期待したメソッド」と言えます。(独習PHP第3版より)

使い方としては、まず抽象メソッドを含んだ抽象クラスを定義してあげます。

(CharacterAbstract.php)

<?php
abstract class CharacterAbstract {
    // 抽象メソッドなので中身を持たない
    protected abstract function attack(): int;
}

サブクラスでの定義は先ほどと同じ通りです。

最初の例と異なるポイントは、抽象クラスを継承したサブクラスは「全ての抽象メソッドをオーバーライドしなければならない義務を負う」という点です。
もし全てのメソッドをオーバーライドしていない場合はエラーが表示され、サブクラスをインスタンス化できなくなります。

このように抽象メソッドを使用すれば「特定のメソッドがサブクラスで実装されることを保証できる」ようになります。

インターフェイス

ただしこれでもまだ問題があります。

PHPでは多重継承ができない(複数のクラスを同時に継承できない)ため、ポリモーフィズムを実現したい全ての機能を1つの抽象クラスにまとめなければならない上、サブクラスが必要としない機能までオーバーライドする必要があるというデメリットが存在します。

例えば、

  • Soldierクラスではattack()メソッドとguard()メソッドが必要

  • Wizardクラスではattack()メソッドだけが必要

といったケースの場合、CharacterAbstractクラスにはattack()guard()という抽象メソッドを定義することになります。
そして前述の通り抽象クラスを継承したサブクラスは全ての抽象メソッドをオーバーライドしなければならない義務を負うため、Wizardクラスは抽象クラスであるCharacterAbstractクラスを継承した時点で、必要としないguard()メソッドまでオーバーライドしなければならなくなります。

こうなるとサブクラスの役割がよく分からなくなってしまう上、コードも冗長になります。

そこで、この問題を解決するために使用するものがインターフェイスです。
インターフェイスとは、

(やや強引に言えば)配下のメソッドが全て抽象メソッドであるクラスのこと(独習PHP第3版より)

と言えます。

実際にコードを書いてみます。先ほどのCharacterAbstractインターフェイスで書き換えると以下のようになります。
そしてインターフェイスでは中身を持つメソッドや、プロパティは定義できず、抽象メソッドと定数のみが定義可能です。

(CharacterInterface.php)
<?php
interface AttackInterface {
    function attack(): int;
}

interface GuardInterface {
    function guard(): int;
}

抽象クラスとの大きな違いはインターフェイスは多重継承が可能である点」です。
このインターフェイスの特徴により、以下のようにサブクラスごとに必要なメソッドだけを実装することができます。

そして、インターフェイスに含まれる全てのメソッドを実装する必要がある」という制約上のメリットも受けられます。

(Soldier.php)
<?php
require_once 'Character.php'

// AttackInterfaceとGuardInterfaceを実装している
class Soldier implements AttackInterface, GuardInterface {
    private const SOLDIER_POWER = 2;
    private const SOLDIER_DEFENCE = -1;

    public function atack(): int {
        return self::SOLDIER_POWER;
    }

    public function defence(): int {
        return self::SOLDIER_DEFENCE;
    }
}
(Wizard.php)
<?php
require_once 'Character.php'

// AttackInterfaceだけを実装している
class Wizard implements implements AttackInterface {
    private const WIZARD_POWER = 1;
        
    public function atack(): int {
        return self::WIZARD_POWER;
    }
}

またインターフェイスの機能を受け継ぐことは厳密に言えば「継承する」ではなく「実装する」と呼ぶため、こちらで覚えておくべきかと思います。またインターフェイスを実装するクラスのことを実装クラスと呼びます。

補足

また、以下のコードも見てみます。

<?php
interface AttackInterface {
    function attack(): int;

    function doubleAattack(): int;
}


class Soldier implements AttackInterface, GuardInterface {
    private const SOLDIER_POWER = 2;

    public function atack(): int {
        return self::SOLDIER_POWER;
    }

    public function doubleatack(): int {
        return self::SOLDIER_POWER * 2;
    }
}

class Wizard implements implements AttackInterface {
    private const WIZARD_POWER = 1;
        
    public function atack(): int {
        return self::WIZARD_POWER;
    }

    public function doubleatack(): int {
        return self:: WIZARD_POWER * 2;
    }
}



// 使用する側のコード
public function buttle(AttackInterface $attack) {
    return $attack->atack();
    return $attack->doubleatack();
}

実装したメソッドを使用する側のコードに注目です。
インターフェイスであるAttackInterfacebuttle()メソッドの引数として型指定して受け取ることで、buttle()メソッドの引数はAttackInterfaceを実装しているクラスのオブジェクトであることが確実となります。

そしてbuttle()メソッドで呼び出しているattack()メソッドとdoubleatack()メソッドはAttackInterfaceの抽象メソッドなので、実装クラスには必ず存在するメソッドと言えます。

したがって、使用する側(buttle()メソッド)の引数に渡された変数がSoldierクラスのオブジェクトであろうとWizardクラスのオブジェクトであろうと、使用する側のコードの記述を一切変えること無くメソッドを扱うことが可能になります。使用する側のコードを実装クラスに合わせて変更する必要はありません。

メモ

実際にインターフェイスを使う場合はこんな感じでしょうか?

<?php
class AttackGetting
{
    private $attackType

    public function __construct(string $attackType)
    {
        // Setter
        $this->attackType = $attackType;
    }

     // Getter
    public function getAttackType(): AttackInterface
    {
        if ($this->attackType === 'Soldier') {
            return new Soldier();
        } else if ($this->attackType === 'Wizard') {
           return new Wizard();
        }
    }
}
<?php

// 使用する側のコード
public function buttle(AttackInterface $attack) {
    return $attack->atack();
    return $attack->doubleatack();
}


$attack_getting = new AttackGetting('Soldier');
buttle($attack_getting->getAtacckType());

参考記事

PHPのPshSHで対話型デバッグを行う

composerを使ってインストール

$ composer g require psy/psysh:@stable                                                                                                                       

Changed current directory to /Users/funesakisuke/.composer
./composer.json has been created
Running composer update psy/psysh
Loading composer repositories with package information
Updating dependencies
Lock file operations: 14 installs, 0 updates, 0 removals
  - Locking dnoegel/php-xdg-base-dir (v0.1.1)
  - Locking nikic/php-parser (v4.10.4)

Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 14 installs, 0 updates, 0 removals
  - Installing symfony/polyfill-php80 (v1.22.0): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.22.0): Extracting archive
archive
7 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
10 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

REPLとしてpsyshを使う

これでコンソールで色々試せる。 Tabを押せば関数を調べられる。

$ ./vendor/bin/psysh                                                                                                                                         
Psy Shell v0.10.6 (PHP 7.4.14 — cli) by Justin Hileman

>>> set
set_error_handler     set_file_buffer       set_time_limit        setlocale             settype
set_exception_handler set_include_path      setcookie             setrawcookie

$ ./vendor/bin/psyshではなく、psyshで利用できるようにパスを通す

# $HOME/.composer/vendor/binを追加
export PATH=“$HOME/.composer/vendor/bin:$PATH”
$ psysh                                                                                                                                            
Using local PsySH version at ~/workspace/app/cake_3_tutorial/cake_3_tutorial
Psy Shell v0.10.6 (PHP 7.4.14 — cli) by Justin Hileman


>>> function timesFive($x) {
...  $result = $x * 5;
...  return $result;
... }
>>> timesFive(10);
=> 50

cake consoleもある。php -aでいい気もした。

$ bin/cake console          
You can exit with `CTRL-C` or `exit`

Psy Shell v0.10.6 (PHP 7.4.14 — cli) by Justin Hileman
>>>
$ php -a                    
Interactive shell

php >

デバッガーとしてpsyshを使う

コードの中にeval(\Psy\sh());を記述すれば、そこがブレークポイントの位置となりデバッグを行える。

Psy Shell v0.10.6 (PHP 7.4.14 — cli-server) by Justin Hileman
From src/Controller/ArticlesController.php:60:
    58:     $tags = $this->Articles->Tags->find('list');
    59:
  > 60:     eval(\Psy\sh());
    61:
    62:     // ビューコンテキストに tags をセット
Psy Shell v0.10.6 (PHP 7.4.14 — cli-server) by Justin Hileman
From src/Controller/ArticlesController.php:60:
    58:     $tags = $this->Articles->Tags->find('list');
    59:
  > 60:     eval(\Psy\sh());
    61:
    62:     // ビューコンテキストに tags をセット

$tags
=> Cake\ORM\Query {#85
     (help): "This is a Query object, to get the results execute or iterate it.",
     sql: "SELECT tags.id AS `tags__id`, tags.title AS `tags__title` FROM tags tags",
     params: [],
     defaultTypes: [
       "tags__id" => "integer",
       "tags.id" => "integer",
       "id" => "integer",
       "tags__title" => "string",
       "tags.title" => "string",
       "title" => "string",
         ...
       .....
       .......

$article->id
=> 2

$article->title
=> "おはようだこんばんは"

$this->request->is(['post', 'put'])
=> false

コマンドオプション

Commands · bobthecow/psysh Wiki · GitHub

help
  help       Show a list of commands. Type `help [foo]` for information about [foo].      Aliases: ?
  ls         List local, instance or class variables, methods and constants.              Aliases: dir
  dump       Dump an object or primitive.
  doc        Read the documentation for an object, class, constant, method or property.   Aliases: rtfm, man
  show       Show the code for an object, class, constant, method or property.
  wtf        Show the backtrace of the most recent exception.                             Aliases: last-exception, wtf?
  whereami   Show where you are in the code.
  throw-up   Throw an exception or error out of the Psy Shell.
  timeit     Profiles with a timer.
  trace      Show the current call stack.
  buffer     Show (or clear) the contents of the code input buffer.                       Aliases: buf
  clear      Clear the Psy Shell screen.
  edit       Open an external editor. Afterwards, get produced code in input buffer.
  sudo       Evaluate PHP code, bypassing visibility restrictions.
  history    Show the Psy Shell history.                                                  Aliases: hist
  exit       End the current session and return to caller.                                Aliases: quit, q

参考

【現代語訳】スタートアップの情報収集のすヽめ

スタートアップの情報どこにあるねん


スタートアップってリリースされている記事とかが少ないので、面白い企業を探したくても見つからないことがよくあるんですよね。

そこで「どんなメディアを使ってスタートアップを探せばいいの?」という方向けに、私がオススメするスタートアップ特化型メディア・サービスを紹介していこうと思います。
(※どんな観点でスタートアップを探すべきか?は読者の目的によって異なると思うので説明しません。その点ご了承ください)

1. STARTUP DB


startup-db.com

10,000社以上のスタートアップ・ベンチャー企業のデータと、企業家・投資家のインタビューが掲載されています。企業の登録数は圧倒的ですね。
老舗?企業、メガベンチャーとか何でも掲載されてる感じもしますが、創業1ヶ月のスタートアップの企業情報も掲載されてます。すごい。

「業界のカテゴリー」で企業を検索できます。

f:id:ryota21silva:20201210235343p:plain

「資金調達額」「従業員数」「設立年数」「サブカテゴリー」などで、詳細検索もできます。

f:id:ryota21silva:20201210235512p:plain

検索結果として、ビャビャーっと企業一覧が表示されます。

f:id:ryota21silva:20201211000026p:plain

プレスリリース、資金調達の最新情報も一覧で見れます。

f:id:ryota21silva:20201210233419p:plain

2. Startup Times


startuptimes.jp

個人的にコレが一番好きです。
スタートアップのインタビュー記事が掲載されているサイトです。とにかくスタートアップに特化してます。リリースしたばかりのサービスが取り上げられている印象です。ワクワクします。

f:id:ryota21silva:20201211001157p:plain

3. BRIDGE


thebridge.jp

「起業家と投資家を繋ぐ」テクノロジー&スタートアップ関連の話題をお届けするブログメディア。
スタートアップの資金調達、業務提携とかのニュースがよく掲載されている。

f:id:ryota21silva:20201214225949p:plain

4. TechCrunch


jp.techcrunch.com

定番のヤツ。

業界別のカオスマップも見れて便利です。EdTech、飲食インフラ、キャッシュレス、副業系サービスなどなど興味のある業界のマップを探してみましょう。

f:id:ryota21silva:20201214230728p:plain

f:id:ryota21silva:20201214231601p:plain

「TechCrunch Tokyo」「Startup Battle」というイベントもあるようです。設立3年未満のスタートアップがビジネスアイデアを持ち寄ってピッチを繰り広げるイベントで、「こんなアツい会社あるんやすげええぇぇ」ってなります。

f:id:ryota21silva:20201214230525p:plain

f:id:ryota21silva:20201214230624p:plain

5. CoralCapital


coralcap.co

こちらも有名ですね。
VCである株式会社Coral Capitalがスタートアップにまつわる情報を発信してくれています。

たとえば、「Coral Insights」の記事は日本のスタートアップ市場に関する記事や、VCはどのような観点をもとにスタートアップに出資しているのかといった話など、勉強になるものが多いです。

ベンチャー企業とスタートアップは何が違うのか? | Coral Capital

私が日本のスタートアップ投資の魅力を海外に伝えるときに話すこと | Coral Capital

f:id:ryota21silva:20201214234424p:plain

「Coral Careers」には、スタートアップキャリアイベント「Startup Aquarium」などスタートアップの転職イベントの情報が配信されてたりします。
他にもCoral Capitalの投資先スタートアップの開発環境を一覧化したマップが掲載されていたり、面白いサービスが色々あります。

f:id:ryota21silva:20201214235017p:plain

6. YOUTRUST


youtrust.jp

「転職市場に出てきづらい優秀な人材を採用できるキャリアSNS」を謳っているサービスです。
従来の転職サービスとは異なりSNSとして利用される側面を持っており、転職者や採用担当者などの呟きがタイムラインに流れているので、そこから興味を持った方に連絡を取ることができるカジュアルなイメージの強い転職サービスです。

f:id:ryota21silva:20211103191129p:plain

転職活動として利用することもできますし、企業一覧ページ には色々な企業が掲載されているので、これを見るだけでも企業探しに使えると思います。 個人的には今時の「イケてそう」なスタートアップが多いイメージです。

f:id:ryota21silva:20211103191711p:plain

7. ウメキワークス


note.com

ウメキワークスとは、個人投資家でもある梅木雄平さんが発信しているスタートアップの資金調達情報やIPO分析などの月額マガジンのことで、成長性のある選りすぐりのスタートアップの情報が多数掲載されています。
f:id:ryota21silva:20211103192818p:plain

ただし情報の質が高い分月額3,000円の購読料がかかってしまうので「ずっと課金し続けるのは厳しい、、」という方もいらっしゃると思いますし、はたまた「そんないっぱい記事見る余裕無いわ」という方もいらっしゃると思います。

そんな方におすすめのウメキワークスでの情報収拾法は、「3,000円の購読料を一度だけ支払って、スタートアップ転職おすすめ企業50選【20XX年版】高野氏監修付き(1.5万字)」だけを読むという方法です。

この「スタートアップ転職おすすめ企業50選【20XX年版】高野氏監修付き(1.5万字)」は、数あるスタートアップの中から転職におすすめの企業を50社掲載した記事となっており、以下のようなポイントから企業を選定しています。

  1. スタートアップ転職の成功≒転職先スタートアップの成功と仮定し、IPO確度がある程度高そうな企業を中心に選定
  2. とはいえレイターステージはあまり旨みがないため、シリーズA,Bを中心に選定。レイターとアーリーも含まれますが、中心はシリーズA,B
  3. 2020年度版と被りすぎても情報価値がないので、昨年と同じ企業は50社中9社に抑えた。言い換えると、その9社は2年連続の選定のため、本誌では強くオススメ
  4. ポジションはビジネスサイドメインの記載だが、エンジニアはどこでも募集していると思われるため、ピックアップしたシリーズA以降のスタートアップであればエンジニアにとっては高い確率で勝ち馬に乗りやすいと想定。同様に、コーポレート部門も大抵募集がある
  5. オススメの読み方は、見出しにあるEC/メディアなどの項目をクリックし、セクターごとのおすすめ企業とポジション一覧をまず見て、興味ある企業について読んでいく。表の赤いセルは2020年選出企業、紫のセルは高野氏オススメ企業です。

    (スタートアップ転職おすすめ企業50選【2021年版】での企業選定ポイント)

本来こちらは1記事で10,000円/の料金設定となっていますが、ウメキワークスの購読料3,000円を支払えば無料で読むことができるので、1ヶ月だけ課金をすれば優良スタートアップ50社の情報を一気に収拾できるというワケです。

時短術!! feedlyを使って複数メディアの記事を一括確認する


ここまで色々なメディア・サービスを紹介してきましたが、これだけのサイトを全部調べるのは面倒臭いですよね。

そこでオススメなのがfeedlyというサービスです。

feedly.com

feedly」とは、登録したホームページやWebメディアの情報を効率よく収集できる非常に便利なサービスです。
例えば、上記の複数メディアをfeedlyの「Start up」タグに登録しておけば、各メディアのページにアクセスすることなく、下記のように一覧化された記事から気になるものを探して読むことができます。

f:id:ryota21silva:20201215004742p:plain

feedlyの使い方は、以下の記事を見れば事足りるかと思います。

ferret-plus.com

こんなところで終わりたいと思います。
お読みいただきありがとうございました。

【備忘録】チェリー本第3章:テストを自動化する

3.1 イントロダクション

3.1.2 プログラマの3大美徳

以下の3つを持つプログラマは良いプログラマと言われる。

1. 怠慢(Laziness)

全体の労力を減らすために手間を惜しまない気質。
何度も同じことをやりたくない面倒くさがりのプログラマは、自動化やDRY原則・再利用を意識してコーディングする。

2. 短気(Impatience)

コンピューターの動作が怠慢な時に感じる怒り。 挙動がおかしくなったり、処理速度が遅くなることに苛立ちを感じるプログラマは、事前にそのような問題を想定してコードを書くようにする。

3. 傲慢(Hubris)

自分の書いたプログラムは誰に見せても恥ずかしくないと胸を張れる自尊心。 傲慢なプログラマは、自分の書いたコードに責任を持ち、さらに保守性を上げるために努力する。

【備忘録】チェリー本第2章:Rubyの基礎

2.6 メソッドの定義(P.36)

メソッドの戻り値

Rubyは最後に評価された式がメソッドの戻り値になるため、returnのようなキーワードは不要。
以下のように、returnはメソッドを途中で脱出したい場合に使用することが多い。

irb(main):012:1* def greeting(country)
irb(main):013:1*   return '入力してください' if country.nil?
irb(main):014:1*
irb(main):015:2*   if country == 'japan'
irb(main):016:2*     'こんにちは'
irb(main):017:2*   else
irb(main):018:2*     'hello'
irb(main):019:1*   end
irb(main):020:0> end
=> :greeting
irb(main):021:0>
irb(main):022:0> greeting(nil)
=> "入力してください"
irb(main):023:0> greeting('japan')
=> "こんにちは"

2.10 真偽値と条件分岐についてもっと詳しく

2.10.5 条件演算子(三項演算子)

式 ? 真だった場合の処理 : 偽だった場合の処理

irb(main):025:0> n = 15
irb(main):026:1* if n > 10
irb(main):027:1*   '10より大きい'
irb(main):028:1* else
irb(main):029:1*   '10以下'
irb(main):030:0> end
=> "10より大きい"

上記の条件分岐を、条件演算子で書けば以下のような感じ。

irb(main):031:0> n = 15
irb(main):032:0> n > 10 ? '10より大きい' : '10以下'
=> "10より大きい"

条件演算子の結果を代入することも可能。

irb(main):033:0> n = 15
irb(main):034:0> message = n > 10 ? '10より大きい' : '10以下'
irb(main):035:0> message
=> "10より大きい"

シンプルなif文は条件演算子ですっきり書けるかもしれないけど、複雑な条件文で使うとかえってコードの可読性が悪くなる場合もあるから注意。

2.11 メソッド定義についてもっと詳しく

2.11.1 デフォルト値付きの引数

引数なしで呼び出した場合もエラーにならない。

def メソッド(引数1 = デフォルト値1, 引数2 = デフォルト値2)
  # 処理
end
irb(main):036:1* def greeting(country = 'japan')
irb(main):037:2*   if country == 'japan'
irb(main):038:2*     'こんにちは'
irb(main):039:2*   else
irb(main):040:2*     'hello'
irb(main):041:1*   end
irb(main):042:0> end
=> :greeting
irb(main):043:0> greeting
=> "こんにちは"
irb(main):044:0> greeting('china')
=> "hello"

2.12 その他の基礎知識

2.12.5 参照の概念を理解する

Rubyの変数にはオブジェクトそのものではなく、オブジェクトへの参照が格納されている。
ある変数をほかの変数に代入したり、メソッドの引数として渡すと、新しい変数やメソッドの引数は元の変数のオブジェクトを参照している。

どのオブジェクトを参照しているかは、object_idメソッドを使えばわかる。

irb(main):048:0> a = 'hello'
irb(main):049:0> b = 'hello'
irb(main):050:0> c = b
irb(main):051:0> a.object_id
=> 180
irb(main):052:0> b.object_id
=> 200
irb(main):053:0> c.object_id
=> 200

2.12.7 require

組み込みライブラリでない標準ライブラリやgemを利用する場合は、明示的にそのライブラリを読み込む必要がある。
自分で作成したRubyプログラム(クラスの定義など)を読み込む場合も必要。

require ライブラリ名