読者です 読者をやめる 読者になる 読者になる

ヘキサゴナルアーキテクチャとレイヤードアーキテクチャの違いとマイクロサービス

programming architecture

ヘキサゴナルアーキテクチャレイヤードアーキテクチャのざっくりとした違いとヘキサゴナルアーキテクチャの利点を説明する。

ヘキサゴナルアーキテクチャは主に六角形で描かれる、多角形の全辺が外部とのインターフェイスであり、内部にさらにアプリケーションレイヤーやドメインコンテキストといった1つ以上のレイヤーを持つアーキテクチャである。 層構造はレイヤードアーキテクチャと同じであり、要はレイヤードアーキテクチャを最下層を軸に一回転させて全方位に対して層構造の性格が変わらないようにしただけである。

ヘキサゴナルアーキテクチャは実装においてはレイヤードアーキテクチャと同じだが、設計を行う上でいくつかの認知的効果がある。

複数インターフェイスを表現しやすい

レイヤードアーキテクチャではインターフェイスは直線または長方形1つのインターフェイスレイヤーで表現されるため複数インターフェイスを定義するにはこの直線や長方形を細切れに分割していかなければならず複数インターフェイスを表現しにくかった。

ヘキサゴナルアーキテクチャでは各面によって異なるインターフェイスを表現できるため複数インターフェイスを設計しやすくインターフェイス複数持つ設計に積極的になれる。

インターフェイスビジネスロジックの分離が促進される

レイヤードアーキテクチャではインターフェイスレイヤーにビジネスロジックなど他のレイヤーの責務が混入するしやすく異なるインターフェイスを追加する障害となっていた。

ヘキサゴナルアーキテクチャは全周囲からアクセスが行われることが視覚的に明白であるためサービスがどのインターフェイスにおいても等しく機能しなければならないという設計要件を効果的に認識させることができる。開発者は常にいずれかひとつの面だけでなくすべての面からの入力に対して等しく機能する内部レイヤーを作らなければならないというイメージを持って開発することになるため抽象化が促進されインターフェイスレイヤーが適切に構築されるようになる。

なお内部レイヤーについてはインターフェイスと同じく六角形で表現する例がよくあるが円形で表現することを勧める。内部レイヤーは基本的にすべてのインターフェイスに対して同じ共通のAPIを提供すべきであり、インターフェイスごとにAPIを変える設計というのはナンセンスだからである。

複数サービス間の連携を表現しやすい

レイヤードアーキテクチャは上下の方向の概念があるため複数アーキテクチャを連携のためにそのまま並べようとすると上下へアクセスの端点を接続できる配置を意識しなければならない煩雑さがある。これはサービス同士の関係がピラミッド型の厳格な階層構造である場合は都合がいいがネットワーク型である場合は向いていない。

ヘキサゴナルアーキテクチャは全周囲にアクセスの端点を持てるためネットワーク状のサービス連携を表現しやすい。

なお通信を行うレイヤーがインターフェイスであるかドメインまたはインフラであるかの差異を多角形の上下または左右で表現する例がよくあるが端点を外周のインターフェイスに持つか内部レイヤーに持つかで表現することを勧める。このような図は互いの位置関係を気にすることなく依存関係を表現することができるからである。たとえばサービスAの内部レイヤーとサービスBのインターフェイスが線で結ばれているならば位置関係に関係なくAはBに依存している。

マイクロサービスと相性がいい

上記のような複数サービス間の連携の表現しやすさはマイクロサービスの設計と相性がよい。マイクロサービスの連携を設計するときは見た目だけでもヘキサゴナルアーキテクチャで表したほうがいいだろう。実体は後から実装すればよい。

まとめ

要はレイヤードアーキテクチャを正しく実装するためのより正確で効果的な視覚表現がヘキサゴナルアーキテクチャである。レイヤードアーキテクチャと比べて特に変わったことをしているわけではない。

TypeScriptで形式的証明・交差型編 ~ 状態付きの型による高信頼領域の構築

TypeScript formal-proof

TypeScriptで交差型を使って静的型に状態的な型を動的に付与していきます。 これによりサニタイズ済/未サニタイズエンコード済/未エンコード、不変/可変、通常文脈/エラー文脈、Truthy/Falsyといった状態を複合的に扱えるようになります。 さっそく試してみましょう。

この記事はTypeScript アドベントカレンダー2015 2日目の記事です。

サニタイズ済みである文字列のみを受け取る型を作ってみます。

declare class Sanitized {
    private id;
}
function sanitize<T>(data: T): Sanitized&T {
    return <Sanitized&T>data; // any processing
}

var plain = '';
var secure: Sanitized&string = plain; // type error
var secure: Sanitized&string = sanitize(plain); // ok

たったこれだけです。たったこれだけで信頼境界の内側と外側を分離できるのです。もはやただの文字列は安全な文字列を要求する型に入れることができなくなりました。

見た目が通常の型宣言とよく似ているためアノテーションとしても利用できます。それでいて、この手法は型のみで実現され実装に影響を及ぼさないためまったく何のオーバーヘッドもありません。

信頼境界を越えるときに安全な文字列に変換し、境界内では安全な文字列だけ使うように制限すればプログラムが確実に安全なデータだけを使っていることが保証されるようになります。

苦労して経路を網羅しなくても受け取り側が型を見て不正なデータを拒否するので、境界内が要件を満たさない不正なデータで汚染されることはありません。

信頼境界内は常に安全なデータだけで満たされているクリーンな領域となります。

このような状態付きの型を定義する方法は他にもいくつかありますが、ここで紹介した交差型を使用した方法はその中でも汎用性が高く開発者の負担が小さいものです。今日からでも使い始められるでしょう。

上記のコードは最低限の機能しかないので、以下の条件を追加してもう少し実用的にしてみます。

  • voidを底である不正な型とする。
  • voidを含む型はvoidとする。
  • 二重に状態を付与しようとした場合はvoidとなる。
export default {
    type,
    extract
};

export declare class Sanitized {
    private SANITIZED;
}

export function type<T>(target: void): void
export function type<T>(target: Sanitized&T): void
export function type<T>(target: T): Sanitized&T
export function type<T>(target: T): Sanitized&T {
    return <Sanitized&T>target;
}

export function extract<T>(target: Sanitized&T): T
export function extract<T>(target: Sanitized&T): T {
    return <T>target;
}

プリミティブ型の次はデータ型にも対応できるようオブジェクトに状態を持たせてみましょう。 すいません動くコードロストして再実装できなくてロストテクノロジーになりました。以下のコード実装できた方いたら教えてください(泣)。 データ型については信頼できるデータのみを保持する信頼境界内用の公称型を用意して信頼性を確保してください。

declare class Sanitized {
    private id;
}
function sanitize<T>(data: T): Sanitized&T {
    return <Sanitized&T>data; // any processing
}
class Data<T> {
    constructor(public value: T) {
    }
    public sanitize(): Data<Sanitized&T> {
        return new Data(sanitize(this.value));
    }
}
interface Data<T extends Sanitized> {
    sanitize(): void;
}

var plain = new Data('');
var secure: Data<Sanitized&string> = plain; // type error
var secure: Data<Sanitized&string> = plain.sanitize(); // ok
var secure: Data<Sanitized&string> = plain.sanitize().sanitize(); // type error

これまでは信頼境界内の信頼性を境界通過時の検査のみで担保・依存し、ひとたび境界内に入ればあとはフリーパスになって不正なデータが混入しても検出できないという状態に甘んじていたこともありましたが、型に状態を持てるようにすることで要求される状態を持たない値が境界内で誤って使われようとしても要件を満たせず拒否される信頼性の高い領域を構築できます。

これまで境界線という線で確保していた信頼性と安全性を境界内の空間全体というより高い次元で確保できるようになったのです。より応用的には個々のデータのうち特に信頼性を求める場合は専用に公称型のデータ型を用意することでさらに確実な操作を行い、それ以外のデータは状態の要件により信頼性を確保するといったアプローチが望ましいでしょう。

今回の内容は単純な状態遷移をモデルへ部分的に適用しただけなので形式的証明と呼べるほどのものではありませんでしたが*1、実装に高水準の信頼性を与える方法として形式的証明と同様非常に効果的なものです。

外部と通信を行うアプリケーションは規模の大小にかかわらずサニタイズと正規化を厳しく要求されるため本稿で紹介したように形式的に信頼性を保証する方法が非常に重要となってきますので、これを期にぜひ自身のプロダクトの信頼性と品質の向上に取り組んでみてください。

TypeScriptアドベントカレンダー2015、需要と余裕があれば次回は状態遷移モデルの形式的証明を行う並行処理および並列処理の形式的証明(状態遷移編)です。予定は未定。

*1:形式的証明は未証明部分で破綻していては意味がないので原則として証明対象の全機能を証明します

TypeScriptで形式的証明・直和型編 ~ じゃんけんゲーム最強トーナメント

TypeScript formal-proof

TypeScriptで形式的証明を取り入れたプログラミングを実践します。

形式的証明とは、仕様またはモデルが数理論理学的に正しいものであり実装可能であることの形式手法による証明です。 本稿では形式手法のうち、型システムを利用した軽量形式手法による形式的証明を扱います。

この記事はTypeScript アドベントカレンダー2015 1日目の記事です。

TypeScriptアドベントカレンダー2015!!

f:id:falsandtru:20151130044653j:plain

去年あれだけいた戦士たちはどこへ行ったのか…

さて、形式的証明を実感するために1つのコードモジュールを作ってみましょう。

じゃんけんの結果をグー・チョキ・パーの3つの型を使って型推論だけで計算し、不正な入力を実行前に検出する、形式的妥当性の証明されたコードモジュールです。

まず前提となるグー・チョキ・パーの3つの型を作ります。

class Gu {
    private identity;
}
class Choki {
    private identity;
}
class Pa {
    private identity;
}

TypeScriptの型は構造的部分型だから型を区別できないと思っていましたか? TypeScriptには型を一意に識別する方法も用意されており、構造的部分型と公称的部分型の両方をサポートしています。

アクセシビリティ修飾子にprivateまたはprotectedを指定するとそのメンバーが他で定義された同名のメンバーに代入できない一意な識別子となります。

これによりTypeScriptでも公称型または公称的部分型である任意の代数的データ型とその直和型を作ることができるのです(数学的には共通部分のない型同士に限られますが実用上は気にする必要はないでしょう)。

また、TypeScriptのUnion typesは直和型そのものではありませんが直和型としての使い方もできるのでTypeScriptでも直和型の概念に基づいたプログラミングが可能です。

次にグーとチョキならグーが勝つといった論理(規則・仕様)を作ります。

interface Rule<T, U> {
  (a: T, b: U): T;
  (b: U, a: T): T;
}
interface Fight<T, U, V> extends Rule<T, U>, Rule<U, V>, Rule<V, T> {
}
var fight: Fight<Gu, Choki, Pa> = (a: Options, b: Options): any => void 0;
type Options = Gu|Choki|Pa;

最後にこれらの仕様の妥当性確認を行うバリデーションコードを書いて正しく機能しているか確認します。

<Gu>fight(new Gu, new Choki);
<Pa>fight(new Gu, new Pa);
...

これで完成です。さっそく試してみましょう。

var correctWinner: Gu = fight(new Gu, new Choki);
var incorrectWinner: Choki|Pa = fight(new Gu, new Choki); // type error

ちゃんと動いてますね。型だけで結果を得られるので問題によってはアルゴリズムを実装せずに正しい答えを得ることすら可能です。

var winner: Gu = fight(new Gu, new Choki) || new Gu;

型を見てから結果をハードコーディング余裕でした。テストでカンニングして途中計算を書かずに答えだけ書くようなものですね。

でもこれだけではつまらないのでもっとサイズを大きくしてみましょう。じゃんけんトーナメントを開催してみます。

グー・チョキ・パーの各インスタンスが個々の選手(の最初の手)となります。選手を区別したい場合はコンストラクタが初期化パラメータを受け取るようにして選手名などで選手を区別できるようにしましょう。このように実体を区別する必要があるときはさっきのようなずるはできません。

// 2回戦
fight(
    fight(new Gu, new Choki),
    fight(new Choki, new Pa)
); // Gu
// 3回戦
fight(
    fight(
        fight(new Gu, new Choki),
        fight(new Choki, new Pa)
    ),
    fight(
        fight(new Gu, new Pa),
        fight(new Choki, new Gu)
    )
); // Pa

問題なくトーナメントが成立することが証明されていますね。

とはいえ…

fight(
    fight(new Gu, new Pa),
    fight(new Pa, new Gu)
); // type error

Oops! エラーになりました。ドローになり勝ち負けが決まらない場合を考慮していなかったのです。

形式的証明を行っても非網羅的定義までフォローしてくれるとは限りません。Haskellなど一部のプログラミング言語は定義の網羅性を検証してくれますが状態遷移がすべての組み合わせにおいて破綻なく循環することまでは検証してくれません。形式的証明用の言語やツールであればおおむね網羅的な検証を実施する機能を備えていますが、通常のプログラミング言語の型システムを利用するだけではそうした本格的な検証までは行えないのです。あくまでソースコード中で具体的に示された組み合わせの中での検証となり、入力する型を誤った実装や実装不可能な仕様やモデルを作らないための検証です。また、いずれにしても根本的定義ミスには無力なので過信は禁物です。

しかし、トーナメントに欠陥があり成立しないことは事前に判明しました。それも実装アルゴリズムに関係なくテストケースの範囲外であってもです(形式的に正しいことを確認する妥当性確認は行いましたが、実装の正しさを検証する、通常のテストに該当する正当性検証は行っていません。なにしろ実装が空ですから。)。トーナメント中に発覚して興行を中止せざるをえなくなり、大ブーイングを浴びながら出資者からの法的責任追及と集団訴訟に怯えるよりよほどましです。これこそが形式手法と形式的証明の威力です。この欠陥の判明を受けてトーナメントを成立させるためにトーナメントが不成立となる組み合わせを認めないか、ドローの解決方法を定義するかは主催者しだいです。

以上のように、このケースでは形式的証明を導入したことで大きな成果を上げることができました。定義と運用の範囲内で動作可能であることが数理論理学的に証明され、逆に不可能である場合はそれを検出してコンパイルの段階で型エラーとして出力し、期待通り正しく機能したわけです。

宣言的で簡潔な型による抽象化された検証と証明は、アルゴリズムの実装可能性という尺度からソースコード上の仕様ないしモデルの評価を可能にし*1、手続き的になりやすく複雑な実装アルゴリズムの部分的テストよりも高水準な信頼性の指標となります*2

静的型付き言語は静的型により動的型付き言語よりも早い段階で実装の正しさを検査して品質と信頼性を高めてきましたが、形式的証明はさらに早く、実装前の段階で抽象化された仕様やモデルを検証しているのです。

このように形式的証明はコードの品質と信頼性の向上に大きく役立つものです。毎年新しいフレームワークを覚えては使い捨てるより形式手法を1つ覚えるほうがよほどにスキルとコード品質が向上します。形式的証明はこのさき10年でも20年でも生きつづける安定した技術分野です。みなさんもぜひ使ってみてください。

TypeScriptアドベントカレンダー2015、次回は交差型を使って型に状態を動的に付与する形式的証明(交差型編)です。お楽しみに。

本稿に掲載したコードは以下のリンク先で形式的動作を確認できます。

じゃんけんゲーム

*1:副作用だらけの言語でも型の世界では形式的証明に適した純粋関数プログラミングを実現できるのです。

*2:形式的証明は入出力の型以上の実装の正しさをテストしないので実装のテストも依然として必要です。

gitで流行のコミットメッセージとベーシックなコミットメッセージまとめ

git GitHub

最近気になってたchore(document): update document filesGoogleフォーマットとUpdate document filesなベーシックフォーマットのまとめ。

Googleフォーマット

chore(document): update document files

Angular.jsなど主にJavaScriptのメジャーリポジトリで人気のフォーマット。

github.com

azuさんが概要をまとめているのでこれを引用する。

feat(ngInclude): add template url parameter to events

The `src` (i.e. the url of the template to load) is now provided to the
`$includeContentRequested`, `$includeContentLoaded` and `$includeContentError`
events.

Closes #8453
Closes #8454
                         scope        commit title

        commit type       /                /      
                \        |                |
                 feat(ngInclude): add template url parameter to events

        body ->  The 'src` (i.e. the url of the template to load) is now provided to the
                 `$includeContentRequested`, `$includeContentLoaded` and `$includeContentError`
                 events.

 referenced  ->  Closes #8453
 issues          Closes #8454

Karmaにキーワードの一覧などがまとまっている。

<type>(<scope>): <subject>

<body>

<footer>

Allowed <type> values:

  • feat (new feature for the user, not a new feature for build script)
  • fix (bug fix for the user, not a fix to a build script)
  • docs (changes to the documentation)
  • style (formatting, missing semi colons, etc; no production code change)
  • refactor (refactoring production code, eg. renaming a variable)
  • test (adding missing tests, refactoring tests; no production code change)
  • chore (updating grunt tasks etc; no production code change)

Example <scope> values:

  • init
  • runner
  • watcher
  • config
  • web-server
  • proxy
  • etc.

The <scope> can be empty (eg. if the change is a global or difficult to assign to a single component), in which case the parentheses are omitted. In smaller projects such as Karma plugins, the <scope> is empty.

あらかじめコミットメッセージ(タイトル)に使用するキーワードが絞り込まれててタイトルに悩まず手早くコミットしていけるのがよい。 スコープの絞り込みも便利。

github.com github.com

コミットログからチェンジログを生成するconventional-changelogをあわせて使うとさらに強力。

github.com

ベーシックフォーマット(Gitフォーマット)

Update document files

本家本元Gitの由緒正しいフォーマット。

github.com

フォーマットのルールがまとまったよい翻訳記事があるのでこれを引用する。

  1. タイトルの後は1行空けて本文を書く
  2. タイトルを50字以内におさめる
  3. タイトルの文頭を大文字にする
  4. タイトルの文末にピリオドを付けない
  5. タイトルは命令形で記述する
  6. 本文は1行あたり72字以内におさめる
  7. 本文ではどのようにではなく何をとなぜを説明する

ルールのいくつかはGoogleフォーマットにも当てはまる基礎基本。

postd.cc

TypeScriptのvoid型との付き合い方 ~ あるいはundefinedを使うな

programming TypeScript

TypeScriptは後付けの型とはいえ信頼境界の内側に入れば基本的にboolean型を宣言した変数に真理値以外が入ることはない。 nullundefinedおよびany型(細かいことを言えば{}型とObject型も)さえ使わなければ型を信じることができる。

と思ってたのだが言語設計的にはそうでもないようだ。

前述のようなバッドプラクティスを避けても、void型については以下のように他のすべての型が入る可能性がある。

function f(): void {
}
function g(): number {
    return 1;
}
function dispatch(callback: () => void): void {
    return callback();
}

var a: void = dispatch(f); // undefined
var b: void = dispatch(g); // 1

これはvalidなコードだが変数bにはvoid型を宣言しているにもかかわらず数値が代入される。 void型の変数など通常使わないし使うべきではないが適切なプログラミングを行ってもこの性質が問題となることがある。 戻り値とその真理値的な使用だ。

JavaScriptには戻り値がundefinedか否かで動作が変わるものが少なくない。JQueryのeachメソッドがfalseを返すと中断するのはよく知られていることだろう。

$().each(_ => !dispatch(g)); // trueを返したかった

Promiseのメソッドチェインでもふとした拍子に問題となることがありえる。 また、常に偽と評価されるfalsyな条件式としてハック的に使用されることもある。

() => dispatch(g) || dispatch(f); // 両方呼びたかった

これはまったく型安全ではない。 戻り値の型のvoidは呼び出し側から見ればanyも同然だ。 このvoid型とどう付き合っていけばいいのだろうか。

思うに、void型の唯一の値であるundefinedはTypeScriptでは未定義という状態を設定するためにのみvoid演算子または空により未定義を逐次生成して使用するべきであり、値として使うべきではない。 そしてvoid型とは値がundefinedである安全や信頼を確保するために使うものではない。

未定義の状態の設定とは関数でreturnを使いながら使わなかったときと同じ結果(戻り値)を作ったり、変数やプロパティの値をやむをえず明示に初期化するような操作を意図している。

他の型は前述のバッドプラクティスを避けて自分で安全を確保すれば、たとえばboolean型であればtrueまたはfalseのいずれかである安全と信頼を確保できる。しかしvoid型は関数の呼び出し過程で他のあらゆる型の値が混入する可能性があり、これはanyやnullなどの特定のキーワードの禁止といった比較的簡単な方法では回避できない。void型は他の型とは意味も性質もまったく異なるのだ。

TypeScriptにおけるvoid型の意味の1つは、値の破棄の要求である。 関数の戻り値に限らず、すべてのvoid型の値は再利用の余地なく即座に破棄しなければならない。 他の型のように型に任せるのではなく、型のためにスタイルの変更が必要となる規約のようなものとして付き合わなければならない。 戻り値の型がvoidの関数はいっそすべてvoid演算子を噛ませたほうがいいかもしれない。

() => void dispatch(g);
function _(): boolean {
    void dispatch(g);
    return true;
}

TypeScriptにおけるvoid型のもう1つの意味は、ダウンキャスト的な柔軟性の採用である。 すべての値はundefinedに変換できるため戻り値の型のみ異なるすべての関数は戻り値がvoid型である関数に変換できる。 このような変換を想定していると考えれば戻り値におけるすべての型がvoid型に代入できることも、まあわからなくもない。 この手法はハック的だが戻り値の型にvoidを指定することは珍しくないので大量のコードの中から目視で区別して排除するのは不可能に近い。

function f(): boolean {
    return false;
}
function g(): number {
    return 1;
}
function dispatch(callback: () => void): void {
    callback();
}

dispatch(f);
dispatch(g);

こう書かれていると安全かつ少しはわかりやすい。

function dispatch(callback: () => void): void {
    return void callback();
}

こちらは型のみだが、類似の柔軟性をパラメータにおいて見ることができる。 オプションのパラメータを持つ関数は、よりオプションが少ない関数に代入できる。

function f(a?, b?): boolean {
    return false;
}
function g(a?): number {
    return 1;
}
function dispatch(callback: () => void): void {
    callback();
}

dispatch(f);
dispatch(g);

他にも有意な解釈や利用法があるかもしれないが自分がすぐに思い浮かんだのはこれくらいだ。 あとは他にまかせる。

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#325-the-void-type

空間ナビゲーション vs リンクマップ

自分が思い描くところの理想に近い空間ナビゲーションを実装してひと段落ついた。

chrome.google.com

開発に着手する前、自分はVimiumのような網羅的なリンクマップより空間ナビゲーションのほうが直感的で使いやすいと思っていた。しかし実際に使ってみると理想的なナビゲーションとなるケースでなおリンクにたどり着くまでがもどかしい。

Googleの検索結果の3件目を開くキータイプを数えると以下のようになる。

s->s->s->Enterの4タイプ

または

(s->)e->xの2から3タイプ

前者は検索結果間にノイズとなるリンクがある場合さらにタイプ数が増える。サイトによってはこのノイズのせいで目的のリンクになかなかたどり着けない。これを解決するために後者のように補助的なリンクマップ機能をつけているが緊急回避的なものである。

後者は補助的かつ限定的なリンクマップなのでこれをメインに使用するくらいならタイプ数的にも最初からVimiumを使ったほうがいい。

対してVimiumはすべてのリンクが一律に

f->x(x)の2から3タイプ

である。意図しないリンクまで網羅される点が視覚的にわずらわしかったが空間ナビゲーションがこれを超える効率でリンクを選択できるページ上の範囲はごく一部だ。

よって空間ナビゲーションがリンクマップより優れたリンクオープナーとして使うには

  • 偶然空間ナビゲーションがメインのリンクから開始されるページであり
  • そのページとそこでの使い方をユーザーが記憶しておかなければならない

という偶然とマンパワーにたよった不合理な方法によらなければならない。

というのが、このあいだまでの状況だったのだがeeeとEEEコマンドを実装したところ使用感が格段に向上した。

画面内にタグリストのようなテキストリンク集がなければeかeeですべてのリンクを網羅できるしEで外周から攻めることもできるので普段見ている過半数のサイトでもどかしさを感じることがなくなった。

Vimiumがリンクを開くためにfのあと2つのキーを読んで打たなければならないのに対して何も考えずにeを1回から数回叩いて1キー押すだけなのは思考的負担がかなり軽くて使いやすい。

かくして網羅的リンクマップより使いやすい空間ナビゲーションが、部分的リンクマップの力により一応達成された。

Vivaldiのキーボードコマンドによる操作の手引きと評価

Vivaldi TP3

キーボードコマンド操作手引き

操作 推奨コマンド ブラウザ共通 Vivaldi Vimium
タブ開く t,Ctrl+T Ctrl+T - t
タブ閉じる x,Ctrl+W Ctrl+W - x
タブ復元 X,Ctrl+Z Ctrl+Shift+T Ctrl+Z X
次のタブ K,Ctrl+Tab Ctrl+Tab 1,4 K
前のタブ J,Ctrl+Shift+Tab Ctrl+Shift+Tab 2,3 J
新規ウィンドウ Ctrl+N Ctrl+N -
ウェブ検索 t,O,Ctrl+Q Ctrl+L Ctrl+Q O
タブ検索 T - Ctrl+Q T
履歴検索 O Ctrl+H Ctrl+Q O
ページ検索 /,Ctrl+F Ctrl+F - /
リンク選択 WASD,f - WASD f
テキストボックスフォーカス gi - - gi
フォーカス解除 Ctrl+[ - - Ctrl+[
スクロール j - - j
スクロール逆 k - - k
ページスクロール d Space - d
ページスクロール逆 u Shift+Space - u
戻る Z Alt+Left Z H
進む X Alt+Right X L
拡大 0 Ctrl+= 0
縮小 9 Ctrl+- 9
等倍 6 Ctrl+0 6
URLコピー yy - - yy
URLペースト移動 P - - P
ブックマーク Ctrl+D Ctrl+D -

ヒント

  • 空間ナビのWASDはVimiumをiキーでInsert modeにすればキーバインドが競合しない
  • Ctrl+[でフォーカス解除できる

欠けてるビルトインコマンド

  • スクロール
  • テキストボックス選択
  • フォーカス解除
  • クイックコマンドタブ絞込み
  • クイックコマンド履歴絞込み
  • クイックコマンドブクマ絞込み
  • クイックコマンドメモ絞込み
  • ネルフォーカス
  • パネル操作
  • タブスタック作成
  • タブスタック解除
  • 履歴画面表示

評価

差別化項目の多くが拡張で代替可能である製品としての欠陥がある。 拡張開発者がその気になればChromium上でVivaldiの操作上の利点のほとんどを再現できる。 さらに現状ではビルトインが拡張より劣っている。 ビルトインのほうが好ましいとはいえ少なくともビルトインが拡張の同等以上か拡張との相性がChromium以上でなければ使われない。

以下問題点

Vivaldiのウリのクイックコマンドだが実はタブを検索できる以外Ctrl+Lでアドレスバーから検索するのと変わらない

ウェブ検索がサジェストが使えないぶん完全なキーワード入力を要求されタイプ数が増えるので圧倒的に不便

タブ検索は組み込みとしてはVivaldi固有だがタブだけ絞り込めないので拡張のVimiumのほうが使いやすい

組み込みの履歴検索のショートカットが上書きされて使用不能

空間ナビゲーションが唯一の希望だけどこれも拡張で簡単に実装できるので危ういオリジナリティ

右クリックメニューに翻訳がない(鯖構築してると英語以外にロシア語やイスラエル語もぶつかることがある)

縦タブとパネル以外VivaldiにあってChromiumで実現不可能なブラウザ機能がない

ぶっちゃけ縦タブもタブ検索時に全件リストアップされれば常時表示されてなくてもいいのでキーボーダーには不要

結論

機能的には拡張作られるだけで吹き飛ばされかねない危ういブラウザ。 Vimiumに空間ナビを追加できるブラウザとしては悪くない。 まずVimiumありきで作って最終的にすべてビルトインでできるようにするロードマップがベスト。 クロスプラットフォームのメーラとしてもいい選択肢になるはず。 でもやっぱりサジェスト使えなくてタイプ数増えるのがブラウザコンセプト的にありえない。新規タブでGoogle開けばサジェスト使えるはずだが変なページ開いて検索できない(正式版で設定できるようになるれば問題ない)。 メジャーなChromium系ブラウザとしての役割も担えるのでコンセプトが無視されてもそこそこ使われて死にはしないと思う。