サーバーサイドエンジニアの技術メモあれこれ

調べたことのメモがき。その他雑記もちょろちょろと。ゆるくやってます。

Capn protoメモ

What is Capn prot

capnproto.org

メモ

Capn protはなぜ早い?

シリアライズ不要

シリアライズしたオブジェクトから直接データを読み取れるようになってる。

タイムトラベル(Promise Pipelining)

合成関数のような形でRPCの返り値を利用したRPCを実行する際、1度目のRPCは返り値を待つことなく後続の処理を実施可能。

capnproto.org

インストール

基本的にソースからビルドする。パッケージ化は公式には実施されておらず有志が行なっているらしい。rpmはない。

capnproto.org

サンプルプログラム

エンジニアがよく聞く用語メモ

What is this?

エンジニアとして働いててよく聞くけど実はどういうものかよくわかってないものをかき集めてみた

メモ

依存性の注入

クラス間を疎結合に保つための考え方。 イメージがつきにくければ、「他クラスのインスタンスをコンストラクタやメソッドの引数として受け取り、具体的な実装は他クラスに出してしまう」と考えれば良い。 これはいわゆる関心の分離に相当するもので、クラスの責任分解を考える時に重要な概念である。

この考え方を導入すると、他クラスに移譲したロジックに関しては置き換えが可能になるため、テストがとてもしやすくなる。

依存性逆転の原則

スローガン的に「具体が抽象に依存する」という説明がなされることが多い。 これもパッと聞くとよくわからないが、

具体→ きっちり実装されてるソースコード 抽象→ インターフェースのように実装の中身は定義されてなくて単体では使えないソースコード

と置き換えればわかりやすい。 つまるところ、 あらかじめ設定されたルールに則って具体的な実装を作り込むようにしましょうね、ということ。

例えば以下のような感じ。(例はChatGPTにて出力してみた) 依存性の逆転が考慮されてない状態

class MySQLDatabase {
    public void save(String data) {
        // Save the data to a MySQL database
    }
}

class HighLevelModule {
    private MySQLDatabase database;

    public HighLevelModule() {
        this.database = new MySQLDatabase();
    }

    public void process(String data) {
        // Some high-level processing

        this.database.save(data);
    }
}

これを依存性の逆転を考慮した状態にすると、以下になる。

interface Database {
    void save(String data);
}

class MySQLDatabase implements Database {
    public void save(String data) {
        // Save the data to a MySQL database
    }
}

class HighLevelModule {
    private Database database;

    public HighLevelModule(Database database) {
        this.database = database;
    }

    public void process(String data) {
        // Some high-level processing

        this.database.save(data);
    }
}

前者の場合、HighLevelModule(ビジネスロジックが反映されたアプリケーションの挙動を表すコード)が、MySQLDatabaseクラスのインスタンスを内部で生成している、すなわちMySQLDatabaseクラスに依存してしまっている。

これを、後者はDatabaseインターフェースを用意し、引数としてDatabseインターフェースをimplementsしているインスタンスを受け取ることにより具体的なデータベースハンドリング処理をもたないようにしている。 Databaseインターフェースをimprementsしたclassであればなんでも使える。Databaseインターフェースをimplementsしているならsaveメソッドが必ず実装されているためである。

今回は依存性注入によって依存解決を図っているが、これに限らず依存性逆転時に依存関係を解決する手段は存在する。 例えばファクトリーパターンが良い例。

interface Database {
    void save(String data);
}

class MySQLDatabase implements Database {
    public void save(String data) {
        // Save data to MySQL database
    }
}

class PostgreSQLDatabase implements Database {
    public void save(String data) {
        // Save data to PostgreSQL database
    }
}
class DatabaseFactory {
    static Database getDatabase(String type) {
        if (type.equalsIgnoreCase("MySQL")) {
            return new MySQLDatabase();
        } else if (type.equalsIgnoreCase("PostgreSQL")) {
            return new PostgreSQLDatabase();
        } else {
            throw new IllegalArgumentException("Invalid database type");
        }
    }
}
class HighLevelModule {
    private Database database;

    public HighLevelModule(String databaseType) {
        this.database = DatabaseFactory.getDatabase(databaseType);
    }

    public void process(String data) {
        // Some high-level processing

        this.database.save(data);
    }
}

こう見ると簡単な話で、Databaseインターフェースをimplementsしているクラスが複数存在し使い分けたいのであればファクトリーパターンを使いましょう、というだけ。

リスコフの置換原則

これもお堅い説明が多いが、要するに

スーパークラスを使ってる部分をサブクラスで置き換えられるようにすべき

ということ。 これだけ覚えておけばいい。

トレイト

機能の集合体(基本的にメソッド。定数を持っててもいいんじゃないかと思う)。

クラスの継承とトレイトのミックスインは似てるようで異なる。 本質的な違いは以下なので、ここだけ押さえておくと良い。

  • トレイトのミックスイン→ has-a(can-do)関係を表す。
  • クラスの継承→ is-a関係を表す。

トレイトのミックスインはアビリティを表す。「何ができるか、どんな能力を持ってるのか?」を表すための手段なので、複数のトレイトをミックスインすることが可能。 太郎さんはマラソンもできるし、お店の経営もできる、というのはごく自然だと思う。 そして、この場合マラソン::run()と、お店::run()のように、どの文脈で使われるメソッドなのか明示することで問題なくメソッドを利用できる。

逆に、クラスの継承はアイデンティティを表す。「あなたはどういう存在か?」を表すための手段なので、多重継承のようにアイデンティティが揺らぐような実装は多重継承可能な言語であってもやめた方が良い。

テンプレートメソッドパターン

読んで字の如く、テンプレートとなるメソッドが用意されてるパターン。 親クラスの方で大まかな挙動は示しつつ、具体的な処理の部分はサブクラスに任せる構成を取る。 この結果、基本的な考え方が同じ部分は再実装しなくて済む。

abstract class Recipe {
    // テンプレートメソッド:料理の一連の流れ。料理のテンプレが定義されてる
    final void cook() {
        prepareIngredients();
        cookFood();
        serve();
    }

    void prepareIngredients() {
        System.out.println("材料を準備します");
    }

    // サブクラスで具体的な調理手順を書く
    abstract void cookFood();

    void serve() {
        System.out.println("皿に盛り付けます");
    }
}
class Omelette extends Recipe {
    @Override
    void cookFood() {
        System.out.println("卵と塩を混ぜて、フライパンで焼きます");
    }
}
class Curry extends Recipe {
    @Override
    void cookFood() {
        System.out.println("野菜と肉を切って、鍋で煮ます");
    }
}
public class Main {
    public static void main(String[] args) {
        Recipe omelette = new Omelette();
        System.out.println("オムレツを作ります:");
        omelette.cook(); //abstractクラスのテンプレメソッドを呼び出してる

        System.out.println("\nカレーを作ります:");
        Recipe curry = new Curry();
        curry.cook(); //abstractクラスのテンプレメソッドを呼び出してる
    }
}

この例では、料理のテンプレートを親クラスで用意しつつ、具体的な料理のステップをどうするのかは、カレーやオムレツといった子クラスたちに実装を持たせている。 これにより、子クラスが関心を払うべき点のみに集中し、抽象度の高い部分は意識しなくて良くなっている。

逆にいうと、親クラスに必要以上にロジックを詰め込みすぎて肥大化、いわゆる神クラス化する懸念もあるため、その点は気をつけないとダメ。 親クラスは基本的に具体によりすぎないように注意しつつ、単一責任の原則を守りながら共通処理を提供するという役割にのみ集中させる。 そういう意味ではチームで開発する時にはかなり注意を払う必要があるし、エンハンス開発が活発なチームの場合は抽象度が適切かどうかをエンハンスのたびに見直す意識が必要。

ストラテジーパターン

これも理解が難しそうな考え方だけど、要は

「誰が」「何をする」を分けて考えること

と覚えておけば大体イメージがつくようになる。 「誰」の部分と「何を」の部分の変更が互いに影響を及ぼさないように切り分けをする。

「誰が」を表すクラス内にストラテジーを受け取るコンポジションを用意する。

public interface AttackStrategy {
    void attack();
}
public class SwordAttack implements AttackStrategy {
    public void attack() {
        System.out.println("剣で斬りつける!");
    }
}

public class BowAttack implements AttackStrategy {
    public void attack() {
        System.out.println("弓で矢を放つ!");
    }
}
public class Character {
    private AttackStrategy attackStrategy; // これがコンポジション。ストラテジーパターンの肝。

    public void setAttackStrategy(AttackStrategy attackStrategy) {
        this.attackStrategy = attackStrategy;
    }

    public void attack() {
        attackStrategy.attack();
    }
}
public class Main {
    public static void main(String[] args) {
        Character warrior = new Character();
        
        // 剣で攻撃する戦略をセット
        warrior.setAttackStrategy(new SwordAttack());
        warrior.attack(); // "剣で斬りつける!"と表示

        // 弓で攻撃する戦略をセット
        warrior.setAttackStrategy(new BowAttack());
        warrior.attack(); // "弓で矢を放つ!"と表示
    }
}

ドラクエのようなターン制バトルを想像するとわかりやすいかと思ったので上記の例を出してみた。 上は「何をする」を柔軟に切り替えられる例だが、AttackStrategyをコンポジションとして持ってるクラスなら誰でもこのストラテジーを使えるので、「誰が」の部分にストラテジーが依存はしてないこともわかると思う。

Redisモジュールメモ

What is Redisモジュール

redis.io

メモ

Lua スクリプトとの違い

ざっくり言えば、Luaはお手軽カスタマイズ、Redisモジュールはガッツリカスタマイズ。 性能面ではC言語で実装するRedisモジュールの方が大きく有利。

LuaスクリプトをRedisサーバに送りつけるのに対して、Redisモジュールはサーバに直接組み込む。Redisを拡張するイメージ。 それゆえマネージドサービス(Elasticacheなど)ではLuaは利用できるけどRedisモジュールを利用することはできないというパターンがある。

Redisモジュール作り方

Redisのソースを落としてくる

Redisのソース内にあるredismodule.hが必要なため

github.com

実装する

公式ページの例を見ながら実装する。

bashメモ

What is bash

ja.wikipedia.org

メモ

前提

Linuxコマンドを複数組み合わせて同時に実行する、というのがbashスクリプトの基本的な考え方。 なのでbashスクリプトの構成要素であるコマンドの解説もこのページで兼ねることにする。

指定した条件に合致するファイルの有無を判定する

testコマンドを利用する。 [ ]もtest同様の働きをする。ワンショットで確認する際はtestコマンド、シェルスクリプト内で条件分岐をしたい場合は[ ]を使うとよい。

atmarkit.itmedia.co.jp

コマンドの失敗を検知して別のコマンドを実行する

command1 || command2

command1が終了ステータス0以外の時にcommand2が実行される。

ログイン中のユーザのホームディレクトリを出力する

echo $HOME

任意のディレクトリにパスを通す

カレントディレクトリにパスを通す(自分がいるディレクトリの実行可能ファイルは常に検索可能にする)なら以下

export PATH=$PATH:.

※補足 PATHは以下のようになっている。コロン区切りで実行可能ファイルを配備しているディレクトリが指定されてるので、末尾に:.を書き足している

root@test:~# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

配列

#!/bin/bash
# create array
arr=(hoge fuga bar)

# show specified index
echo ${arr[2]}

# all
echo ${arr[*]}
echo ${arr[@]}

# add single
arr+=("one")
echo ${arr[@]}

# add multiple
arr+=("two" "three")
echo ${arr[*]}

# delete single
unset arr[2]
echo ${arr[@]}

# delete multiple
# nothing

# delete all
unset arr
echo ${arr[*]}

※補足

全要素アクセス時の@と*の違いについては以下を参照。 ダブルクォートで配列を囲った時に違いが出てくる。

「*」の場合は一回でループが終了しています。ダブルクォートで括った配列のインデックスを「*」で指定すると配列全体の一つの値として展開されてしまうからです。「@」の場合は個々の要素がダブルクォートで括られた状態で展開してくれます。

www.task-notes.com

その他参考文献

www.oreilly.co.jp

アルゴリズムメモ

What is アルゴリズム

ja.wikipedia.org

アルゴリズム力を試すいい場所になるのがAtCoder。 筆者もAtCoderで奮闘中。

メモ

UnionFind

用途

グラフ問題で利用する。競プロ外ではDisjointSetというらしい。

  • 閉路を検出したい時

  • 異なる2点が同一の島にいるか確認したい時

がつかいどき。特に閉路検出目的で使うことが多い気がする。 木のバランシングなど教育的な要素があるので一度は自前で実装したいところ。

コード
class UnionFind:
    def __init__(self, node_num) -> None:
        self.parent = [i for i in range(node_num+1)]
        self.rank = [1 for i in range(node_num+1)]

    def isSame(self, a, b):
        return self.__getRoot(a) == self.__getRoot(b)
    
    def unite(self, a, b) -> bool:
        if self.isSame(a, b):
            return False

        self.__merge(a, b)
        return True 
    
    def __getRoot(self, a):
        if self.parent[a] == a:
            return a
        else:
            self.parent[a] = self.__getRoot(self.parent[a])
            return self.parent[a]
    
    def __getRank(self, a):
        return self.rank[self.__getRoot(a)]
    

    def __merge(self, a, b):
        if self.__getRank(a) < self.__getRank(b):
            self.parent[self.__getRoot(a)] = self.__getRoot(b)
        else:
            if self.__getRank(a) == self.__getRank(b):
                self.rank[self.__getRoot(a)] += 1
            self.parent[self.__getRoot(b)] = self.__getRoot(a)

エラトステネスのふるい

内容

n以下の素数を高速に求めることができる。 計算量はO(nloglogn)

コード
def getPrimeListLessThan(n) -> list:
    prime_flag = [True for i in range(n+1)]
    prime_flag[0] = False
    prime_flag[1] = False

    i = 2
    while i*i <= n:
        index = i+i
        if prime_flag[i]:
            while index <= n:
                prime_flag[index] = False
                index += i
        i += 1
    
    prime_list = []

    print(prime_flag)
    for i in range(n+1):
        if prime_flag[i]:
            prime_list.append(i)

    return prime_list.copy()
参考リンク

manabitimes.jp