One Step Ahead

プログラミングやエンジニアリング全般について書いていきます

じぶん Release Notes(ver 0.29.10)

先日、2021/2/28、atEaE (ver 0.29.10) がリリースされました👏

他の人たちに習って自分もリリースノートを作っていこうと思います。 versioningは『0.年齢.月齢』とします!
(引越し作業で疲れて、3/1に公開するのを忘れていました...)

📚Input


【読書】

読書中

読了

【技術関連】

  • Golangの基礎的な部分で覚えの甘い部分(context、channel周り)を復習
  • CSSの基礎を復習(セレクタやスタイル優先順位周り)

📝Output


【はてなブログ】

【Qiita】

【Github】

🚩KPT


【Keep】

  • 毎日コーディングは何とか継続中(masterへのmerge待ち複数)
  • 少ないながらも読書も継続できている。

【Problem】

  • 在宅業務も1年近くになり、時間の使い方のメリハリがなくなっている。
  • 引越しと各種手続きに時間を割かれすぎて、Inputが少なくなってしまった。
  • Outputが棚卸し的な内容ばかりになっている。

【Try】

  • GithubへのOutputを継続する。
  • タイムスケジュールを立てて行動してみる。

🎯リリース概要


振り返ると、2月は非常に忙しい月でした。 まず、転職にあたり日常業務をしながら、引継ぎ資料の作成をしていました。
これだけでも十分にキツイのですが、加えてプライベートでは引越しも控えていたため、荷物の整理と部屋の整理に追われる日々。
加えて、新しい家族(猫)が増えるということで、空いた時間はひたすら猫グッズの検索。
更にさらに個人開発もしたいし、ブログも書かねばということで我ながら2月は少し詰め込み過ぎました。

3月はもう少し、落ち着いてInput,Outputできるようにしたいと思います。

WindowsでPython3系を実行するとMSストアが開く時の対処方法

はじめに


pythonをインストール後、インタプリンタを起動しようするとインタプリンタが起動せず、Microsoft Storeが代わりに起動してしまう。という事象があったので、備忘録として残しておきます。

確認環境


今回の事象が確認できたのは、以下の環境です。

  • Windows 10 Pro
  • Python 3.9.2

VSCodeもしくは、Powershellから直接pythonコマンドを入力すると、Microsoft Storeが起動してしまいます。

f:id:EaE:20210226214116p:plain

pythonコマンドを入力しても、インタプリンタは起動しません。代わりにMicrosoft Storeが起動してしまいます。

f:id:EaE:20210226214238p:plain

対処方法


① パスの順番を入れ替える

方法としては、タイトル通りなのですが、それだけだとあまりにも味気ないので、そこに至る過程についてもざっくりと説明します。

まずは、『pythonコマンドを実行して、Microsoft Storeが起動する。』という事象が確認されているので、実行されているコマンドがインストールフォルダ配下の.exeファイルなのかを確認します。
PowershellではGet-Command(エイリアスはgcm)を使用して、コマンドのパスを調べることができます。

f:id:EaE:20210226221757p:plain

より詳細な情報が知りたい場合は、Format-Listコマンド(エイリアスはfl)を使いましょう。

f:id:EaE:20210226221806p:plain

%USERPROFILE%\AppData\Local\Programs\Python\Python[version]が本来のインストールフォルダになりますが、%USERPROFILE%\AppD[f:id:EaE:20210226222902p:plain]ata\Local\Microsoft\WindowsAppsというフォルダの異なるpython.exeファイルが呼び出されていることが分かりました。

次に環境PATH設定の解決順序を確認します。

f:id:EaE:20210226222029p:plain

↑の通り、Programs\PythonよりもMicrosoft\WindowsAppsの方が解決順序が上に設定されています。この解決順序を変更していきます。

f:id:EaE:20210226222444p:plain

修正後の環境PATHが↑の通りです。先ほど、Microsoft\WindowsAppsより下に設定されていたPythonフォルダへのパス情報をすべて、Microsoft\WindowsAppsより上に設定しています。

f:id:EaE:20210226222902p:plain

最後にpythonコマンドのパスを確認して終了です。

さいごに


  • 環境PATHの解決優先順位を変更する。

これ以外にも解決方法があれば、教えてもらえると助かります。

Swashbuckleを使用して、凝らないSwaggerドキュメントを生成する。(ASP.NET WebAPI偏)

はじめに


ASP.NET WebAPI(.NET Framework)とSwashbuckleをして、コードファーストアプローチからSwaggerドキュメントを生成します。
『何故に今更、ASP.NET WebAPIを使うの?ASP.NET Coreじゃダメなの?』と思った方もいらっしゃるかと思いますが、業務では.NET Frameworkを使う機会がいまだに多いという個人的な事情です...
個人開発であれば、ASP.NET Core一択です。

まずはコードを見せろ。という方はこちらをどうぞ

開発環境


  • Visual Studio 2019
  • .NET Framework 4.7.2
  • C# 7
  • ASP.NET WebAPI
  • Swashbuckle5.6.0

Swashbuckleのインストール


Swashbuckleは.NET用に提供されているSwaggerコードファーストアプローチ用のライブラリです。 今回は、ASP.NET WebAPIで作成していますが、もちろんASP.NET Coreにも対応しています。

PackageManagerから『Swashbuckle』をインストールしてください。

Install-Package Swashbuckle

f:id:EaE:20210223150126p:plain 

凝らないドキュメントの生成


今回は、コードファースト(ボトムアップ型)のアプローチでSwaggerドキュメントを生成していくので、APIのコードが必要となりますが、そこについてはプロジェクトによって十人十色なため割愛します。以降に紹介するコードは、サンプルとして公開しているので、そちらを参考にしてください。 

Swashbuckleをプロジェクトに追加すると、App_Startディレクトリの中にSwaggerConfigが作成されます。下記のようなファイルが追加されているかと思います。(実際に生成されるファイルには大量にコメントがされているはずなので、簡略化しています。)

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

namespace WebAPIExample
{
    /// <summary>
    /// Swagger Configuration.
    /// </summary>
    public class SwaggerConfig
    {
        /// <summary>
        /// Register swagger configuration.
        /// </summary>
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                    {})
                .EnableSwaggerUi(c =>
                    {});
        }
    }
}

ここから特別な設定などは追加せず、凝らないドキュメントの生成とUIの設定を行っていきます。

【ドキュメントの生成】

まずはドキュメントの生成を行っていきます。
ドキュメント生成に関わっているのは, EnableSwagger(c => {})の部分で、この中に設定を追加していきます。実際のAPIに関する基本情報を追記したのが下記になります。

[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]

namespace WebAPIExample
{
    /// <summary>
    /// Swagger Configuration.
    /// </summary>
    public class SwaggerConfig
    {
        /// <summary>
        /// Register swagger configuration.
        /// </summary>
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            GlobalConfiguration.Configuration
                .EnableSwagger("docs/{apiVersion}/swagger", c =>
                    {
                        // api infomation.
                        c.SingleApiVersion("v1", "WebAPIExample")
                            .Description("This repository is example of ASP.NET WebAPI (.NET Framework) with Swashbuckle.")
                            .License(l =>
                                {
                                    l.Name("MIT")
                                     .Url("https://github.com/atEaE-samples/aspnet-webapi-swashbuckle-example/blob/master/LICENSE");
                                })
                            .TermsOfService("There are no restrictions on use.")
                            .Contact(cb =>
                                {
                                    cb.Name("atEaE")
                                      .Email("sample@example.com")
                                      .Url("https://github.com/atEaE");
                                });

                        // use schemes
                        c.Schemes(new[] { "http", "https" });
                    })
                .EnableSwaggerUi(c =>
                    {});
        }
    }
}

EnableSwagger()の第一引数に指定している"docs/{apiVersion}/swagger"文字列は、Swaggerドキュメントのエンドポイントを表しています。{apiVersion}の部分は、後述するAPIVersionが自動的にパスに反映されます。

今回のAPIはシングルAPIなので、c.SingleApiVersion()を使用して、ドキュメントを生成しています。 第一引数に"v1"APIのVersionを、第二引数にWebAPIExampleAPIのタイトルを設定します。
SingleApiVersion()メソッドは、Swaggerドキュメントのinfoに相当するInfoBuilderを返却するため、メソッドチェーンさせることで設定情報を記述することができます。
InfoBuilderで設定可能な項目は下記の通りです。

  • Description() : ドキュメントについての説明
  • License() : APIのライセンス
  • TermsOfService() : 利用規約
  • Contact() : APIについての問い合わせ先

Contact(), License()はそれぞれ、Actionを引数として受け取るため、さらに詳細なプロパティを設定することができます。

c.Schemes()には、APIの通信プロトコルを設定しています。この例では、https, httpの2つを設定しています。

【SwaggerUIの有効化】

何となく気づいている方もいると思いますが、SwaggerUIの有効化はすでに完了しています。
EnableSwaggerUi(c =>{});が設定されているため、SwaggerUIが有効となっています。

ここまでで、ドキュメント生成に関する準備は終了です。

Swaggerドキュメントの確認


最後にSwaggerドキュメントを確認していきます。 サンプルAPIをDebug起動させると、下記のようなIndexページに遷移するかと思います。

f:id:EaE:20210223181245p:plain

Indexページに遷移したら、https://localhost:44377/swagger もしくは https://localhost:44377/swagger/ui/indexに遷移することで、SwaggerUIのインデックスページを確認することができます。

f:id:EaE:20210223181435p:plain

f:id:EaE:20210223181514p:plain

メソッドに応じたパス情報、返却されるモデル情報についても確認することができます。 UIの元になっているSwaggerドキュメントは、先ほど設定したパスにアクセスすることで確認できます。
(パステンプレートに、"docs/{apiVersion}/swagger"を。Versionにv1を設定しているので、https://localhost:44377/docs/v1/swaggerにアクセスすることで、ドキュメントを確認することが出来ます。 )

{
    "swagger": "2.0",
    "info": {
        "version": "v1",
        "title": "WebAPIExample",
        "description": "This repository is example of ASP.NET WebAPI (.NET Framework) with Swashbuckle.",
        "termsOfService": "There are no restrictions on use.",
        "contact": {
            "name": "atEaE",
            "url": "https://github.com/atEaE",
            "email": "sample@example.com"
        },
        "license": {
            "name": "MIT",
            "url": "https://github.com/atEaE-samples/aspnet-webapi-swashbuckle-example/blob/master/LICENSE"
        }
    },
    "host": "localhost:44377",
    "schemes": [
        "http",
        "https"
    ],
    "paths": {
        "/api/Users": {
            "get": {
                "tags": [
                    "Users"
                ],
                "operationId": "Users_Get",
                "consumes": [],
                "produces": [
                    "application/json",
                    "text/json",
                    "application/xml",
                    "text/xml"
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/UserResponse"
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Users"
                ],
                "operationId": "Users_Post",
                "consumes": [
                    "application/json",
                    "text/json",
                    "application/xml",
                    "text/xml",
                    "application/x-www-form-urlencoded"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "value",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/UserRequest"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    }
                }
            }
        },
        "/api/Users/{id}": {
            "get": {
                "tags": [
                    "Users"
                ],
                "operationId": "Users_Get",
                "consumes": [],
                "produces": [
                    "application/json",
                    "text/json",
                    "application/xml",
                    "text/xml"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/UserResponse"
                        }
                    }
                }
            },
            "put": {
                "tags": [
                    "Users"
                ],
                "operationId": "Users_Put",
                "consumes": [
                    "application/json",
                    "text/json",
                    "application/xml",
                    "text/xml",
                    "application/x-www-form-urlencoded"
                ],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    },
                    {
                        "name": "value",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/UserRequest"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    }
                }
            },
            "delete": {
                "tags": [
                    "Users"
                ],
                "operationId": "Users_Delete",
                "consumes": [],
                "produces": [],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    }
                }
            }
        }
    },
    "definitions": {
        "UserResponse": {
            "type": "object",
            "properties": {
                "users": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/User"
                    }
                }
            }
        },
        "User": {
            "type": "object",
            "properties": {
                "familyName": {
                    "type": "string"
                },
                "firstName": {
                    "type": "string"
                },
                "userName": {
                    "type": "string"
                },
                "email": {
                    "type": "string"
                },
                "gender": {
                    "type": "string"
                },
                "age": {
                    "format": "int32",
                    "type": "integer"
                },
                "bio": {
                    "type": "string"
                }
            }
        },
        "UserRequest": {
            "type": "object",
            "properties": {}
        }
    }
}

さいごに


  • シングルAPIでコードファーストアプローチ型のドキュメント生成をするのは簡単
  • 凝ったことをしないのであれば、API情報の設定のみで残りは設定いらず。

参考・引用


Swaggerを使った開発アプローチ

はじめに


Swaggerを使ったAPI開発は、業務でも個人開発でもお世話になることが多いのですが、具体的な『開発手法』『開発アプローチ』について考えたことがなかったので、調べてみました。

2つの開発手法


Swagger 公式Blogや、個人Blogの中には、開発手法に関して言及しているものがいくつか存在します。 それらの中では主に2つの開発手法が示されています。

f:id:EaE:20210221165424j:plain

Design First or Code First: What’s the Best Approach to API Development? より引用

  • デザインファースト(トップダウン型)
  • コードファースト(ボトムアップ型)

の2つが紹介されています。
これら2つのアプローチについて、『どういったものなのか?』『どういった場合に使用が勧められるのか?』ということを次の項から見ていきます。

デザインファースト(トップダウン型)


『OpenAPIの仕様(swagger.yamlなど)から、コードを生成する。』手法を指します。
与えられた要件から、コードの元となるSwaggerドキュメントの設計を行います。Swaggerドキュメントの設計が完了した時点で、それを元にコードを自動生成していくといった形をとります。

適用が勧められるケースとして、下記のようなケースが紹介されています。

①外部ユーザーに対して重要度の高いAPIを公開する場合

外部ユーザーに対して、重要度の高いAPIを公開する場合は、APIの仕様が非常に重要になります。外部ユーザーはAPIの内部構造を覗き見ることができないため、仕様に関するドキュメントが全てとなります。 特に境界面となるインタフェースプロパティ、パス、パラメータは非常に重要なため、予め明文化されたSwaggerドキュメントなどからコードを自動生成する方が、情報の一貫性を保つことができます。

②チーム間での開発コミュニケーションを重要視する場合

開発の分化が進んでいるチームでは、バックエンドとフロントエンドが分かれていることが、ほとんどだと思います。
この場合、1チームや1人の開発者がフロントエンドとバックエンドを一緒に作るわけではないため、APIに関する情報を各チームに対して共有しておく必要があります。
情報共有を『コードを見て判断してください。』や『Excelドキュメントを参照してください。』のようにフォーマットの定まっていない形式で行ってしまうと、情報共有にかかる時間を無駄に費やしてしまうことになります。

Swaggerドキュメントで既定のフォーマットを使用すれば、円滑な情報共有を促すことができます。 フロントエンドはSwaggerドキュメントからエンドポイントの情報や認証情報、型に関する情報を得ることができます。Swaggerドキュメントから簡易的なMockサーバーを生成することもできます。
バックエンドは、そのままAPIを自動生成し、内部の処理を実装するだけで済みます。

コードファースト(ボトムアップ型)


『コードから、OpenAPIの仕様を生成する。』手法を指します。 与えられた要件から、コーディングを開始して機能が完成したところで、コードからSwaggerドキュメントを自動生成していくといった形をとります。

適用が勧められるケースとして、下記のようなケースが紹介されています。

①素早く製品をリリースしたい場合

デザインファーストでは、インターフェースに関わる部分のコード生成を自動化することはできますが、重要な『処理』に関する部分のコードは自動生成できません。
DBからデータを取得する処理や、パラメータに対して何らかの操作を行う処理もすべて自前で記述する必要があります。
そのため、スピードが重視される場面において、ドキュメント生成をしてから、インターフェース部分のコードを自動生成して、インタフェースを崩さないように処理部分を実装する。というプロセスはあまり好ましくありません。

加えて、デザインファーストはあくまでドキュメントから自動生成されたコード群に過ぎないため、運用を考慮したフォルダ構成やアーキテクトになっているわけではありません。
CI/CDによる素早いリリースプロセスを実行していくためには、Toolによる自動化が欠かせませんが、運用を考慮しないフォルダ構成やアーキテクトでは、この自動化が非常に困難になります。
こういった場合は、コードファーストで先に開発を行い、コードからSwaggerドキュメントを自動生成してしまう方が良いとされています。

②PrivateなAPIを開発する場合

外部公開を目的としないAPIや、企業内部で限定的な利用しかしないAPIを作成する場合は、コードファースト型の開発が適しているとされています。 これは、公開APIの開発と比較して、内部限定のAPI開発はインタフェースの規約が緩いことが多いため、仕様に関するドキュメントの重要性がそこまで高くない。というのが理由のようです。

まとめ


  • OpenAPIの仕様から、コードを生成する『デザインファースト(トップダウン型) 』
  • コードから、OpenAPIの仕様を生成する『コードファースト(ボトムアップ型)』
  • どちらが良いではなく、実際の開発に求められる要件に基づいて手法を選択する。

参考・引用

HTMLのタグの分類

はじめに


普段はサーバーサイドばかりで、一向にフロントエンドの知識が身についていないのでHTML、CSSを一から復習し直すことにしました。
ということで、HTML, CSSに関しては当分の間、基礎的なことばかりになると思います。

HTML5以前のタグ分類


HTML5以前は、『ブロック要素』『インライン要素』の2つにタグは分類されていました。

ブロック要素は、1つのまとまり(ブロック)として認識される要素のことで、ブラウザで表示される際には前後に改行が入ります。ブロック要素の中には、別のブロック要素やインライン要素を配置することができます。

<div class="outline">
  <div class="inner">
    <p><strong>Hello world!!</strong></p>
  </div>
</div>

インライン要素は、ブロック要素の内容情報として使用される要素のことです。文章の一部(インライン)として扱われます。ブロック要素とは異なり、前後に改行は入りません。インライン要素の中には、文字や別のインライン要素を配置することはできますが、ブロック要素は配置することができません。

<code>
  <span>import System;</span>
</code>

HTML5で新たに導入された考え方


HTML5では、上記で説明した『ブロック要素』『インライン要素』という分類が廃止になりました。
文書の構造をより明確に表現できるように類似した特性に応じてカテゴライズし直され、新たな分類に分けられています。
(MDNでは、これらをContents categories:コンテンツカテゴリと表現しています。)

『文書の構造をより明確に表現できるように』ということですが、具体例としてこのサイトの簡略図を元に、HTML5以前とHTML5での文書構造の違いを見ていきます。
(理解しやすいように簡略化しています。)

f:id:EaE:20210218133403p:plain

HTML5以前であれば、divタグを使用してidclass属性を使いながら、それぞれのブロックが『何を表しているのか』を表現していました。HTML5以前は、タグだけでは文書構造は分からないため、属性を使用することで開発者が明示的に役割を与える必要がありました。
加えて『役割を与える』といっても、統一された規格に基づいたものではありません。あくまで開発者が、id="header"という属性を付けたから、そのサイトではヘッダとして扱われるというだけです。他のサイトでは、id="head"かも知れませんし、class="header"かも知れません。

f:id:EaE:20210218133900p:plain HTML5では、タグそのものが意味をもっており、idclassといった属性を使用せずに『何を表しているか』を表現できます。そのため、タグのみで文書構造の表現が完結していてかつ、統一された規格に則ったものになっています。headerタグを使えば、どのサイトであれその要素はヘッダーで、navタグを使えば、どのサイトであれその要素はナビになります。
仮にCSSがなかったとしても、HTMLだけで『どこが何を表しているのか』を統一された規格を元にアウトラインを知ることができます。
(divばかり使ってコーディングするな!と怒られた経験のある方もいるかも知れませんが、この考え方を無視してしまっているためだと思われます。)

カテゴリ分類


カテゴリは以下のように分類されます。

f:id:EaE:20210218004719p:plain

HTML Living Standard より引用
https://html.spec.whatwg.org/multipage/dom.html#kinds-of-content
(本家のサイトではSVGにより、アニメーション付きで各コンテンツを確認できます。)

  • メタデータ・コンテンツ
  • フロー・コンテンツ
  • セクショニング・コンテンツ
  • ヘッディング・コンテンツ
  • フレージング・コンテンツ
  • エンベディッド・コンテンツ
  • インタラクティブ・コンテンツ

合計で7つのカテゴリに分類されます。
※ 図からも分かる通り、1つのタグが複数のカテゴリに含まれる場合もあります。

【メタデータ・コンテンツ】

名前の通り『メタデータ』を取り扱うコンテンツ分類になります。
メタデータ以外のコンテンツに対する『表示』『動作』といった設定はここに記載します。
base, link, meta, noscript, script, style, template, titleなどが該当します。

【フロー・コンテンツ】

bodyタグの内部で本文要素として使用されるものがフローコンテンツとなります。ほぼすべての要素がここに該当します。↑の図からも分かるように、残りのコンテンツは、フロー・コンテンツがさらに細分化されるような形になります。 (一部のメタ要素もここに該当します。)

【セクショニング・コンテンツ】

特定の領域を区切る場合、まとまりを定義し、アウトラインを作成する場合に使用されるコンテンツが分類されます。
article, aside, nav, section

【ヘッディング・コンテンツ】

見出しを表す要素がここに分類されます。
h1, h2, h3, h4, h5, h6, hgroup

【フレージング・コンテンツ】

文書テキストや、テキスト内の段落内のマークアップに使用されるタグなどが分類されます。
a, abbr, area, audio, b, bdi, bdo, br, button, canvas, cite, code, data, datalist, del, dfn, em, embed, i, iframe, img, input, ins, kbd, label, link, map, mark, math, meta, meter, noscript, object, output, picture, progress, q, ruby, s, samp, script, select, slot, small, span, strong, sub, sup, svg, template, text, area, time, u, var, video, wbr

【エンベディッド・コンテンツ】

文書構造内に別のリソースをインポートするものなど、埋め込みを行うタグがここに分類されます。
audio, canvas, embed, iframe, img, math, object, picture, svg, video

【インタラクティブ・コンテンツ】

ユーザーに対して対話的なIFを提供するタグがこのコンテンツに分類されます。
a, audio, button, details, embed, iframe, img, input, label, object, select, textarea, video

まとめ


  • HTML5からブロック要素・インライン要素はなくなりコンテンツカテゴリが導入された。
  • 統一された規格に基づき、HTMLだけで文書構造のアウトラインを把握できるようになった。

参考・引用


画面のレスポンスタイム指標

はじめに


パフォーマンスのテスト計画を作成するにあたり、画面のレスポンスタイムに関する指標を調べる機会があったので、そのまとめです。

画面レスポンス(応答性)の早さはどうして重要なのか?


まずはどうして、応答性の早さが求められるのかという点ついてです。
Website Response Times』の中で、応答性について2つの観点から、その重要性を述べていました。

1. 人間の能力的観点

記憶力と注意力といった観点からみても、Webレスポンスが長くなることはあまり好ましくありません。
ワーキングメモリ自体は特定の期間、繰り返し思考されたりしない限り、長期記憶には残らないとされており、ワーキングメモリ内の情報の持続時間はおおよそ10~15秒とされています。そして、もちろん個人差があります。
そのため、10秒を超えるようなレスポンスは作業パフォーマンスの低下を引き起こすとされているそうです。

2. 人間の願望的観点

コンピューターを操作するとき、私たちはコンピューターに対して無意識のうちに従順であることを期待しています。検索の結果を気まぐれに返したり、クリックに反応する場合があったりなかったりすることはなく、私たちが操作した内容に対して、正確かつ迅速で、的確であることを期待しています。
なので、少しでも自分の期待に添わないような動き、例えば...

『クリックに反応しない。』
『検索結果のレスポンスにばらつきがある。』

といった場合には、『期待に背かれた』と感じてストレスを感じてしまいます。

応答時間の制限(3 response-time limits)


応答時間の基準に関しては、1993年の段階で3つの指標が出されています。

応答時間 0.1秒

ユーザーは、自身の操作に対して瞬時に反応しているような感覚を得ます。
リアルタイムの操作性が求められる機能に関しては、この秒数内に収めることがほぼ必須とされます。

応答時間 1秒

1秒程度の応答時間であれば、一瞬の遅延は感じることはあっても作業や思考自体が分断されるような感覚になることはありません。 この程度であれば、ユーザーはコントロール性をもってコンピューターを操作していると感じることができます。

応答時間 10秒

ユーザーの注意力が持続するのは1~10秒程度なので、ここが限界値です。
ユーザーから、ギリギリ許せるか...といったところです。明らかな遅延を感じ、コントロール性は失われているため、ユーザー体験としては決して良いものではありません。

応答時間の制限(RAIL model)


次にRAILモデルからみた、応答時間の制限についてです。
RAILモデルは、Google Web Fundamentalsで公開されている、『ユーザーファーストに基いて考えられた、ユーザーを中心に考えるパフォーマンスモデル』のことです。
このモデルは、以下の4つの要素から構成されています。

  • Response(応答速度)
  • Animation(動き)
  • Idle(静止状態)
  • Load(読み込み速度)

今回はあくまで、『画面のレスポンスタイム指標』がお題なので、Responseについてフォーカスします。

応答時間 0~16ミリ秒

ページ内動作であれは最高です。Good。
ただ、ユーザーはモーショントラッキングに長けているので、『アニメーション』ということであれば話は変わってきます。
ユーザーは毎秒60フレームがレンダリングされている限り、アニメーションに遅延を感じません。 逆にそれを超えるとユーザーは、アニメーションの遅延を認識し始めます。そのため、16ミリ秒という数値はアプリケーションのフレーム間隔でいえばギリギリということになります。

応答時間 0~100ミリ秒

この時間内であれば、ユーザーは即時に応答されていると感じます。これ以上長くなると、ユーザーは自分のアクションとリアクションの乖離を感じ始めます。

応答時間 100~300ミリ秒

ページ内の動作などであれば、ユーザーは遅延を感じ始めます。

応答時間 300~1000ミリ秒

ページの読み込みなど、ページ内動作以外であれば、スムーズな体験を提供しているというレベルです。 ページ内動作であれば、上記同様に遅延を感じ、アクションとリアクションの乖離を感じている状態です。

応答時間 1,000ミリ秒以上

1秒(1,000ミリ秒)を超えると、ユーザーは自身が実行したタスクへの関心を失い始めています。

応答時間 10,000ミリ秒以上

10秒(10,000ミリ秒)を超えると、ユーザーはイライラしてタスクをほぼ放棄している可能性が非常に高いです。ユーザーはもう戻ってこないかも知れません。

2つの指標から見えること


応答時間は場面とタスクに応じて

単純に『応答時間』だけをみて遅い云々の議論はできない。というのは非常に重要なことかなと思います。 上の指標だけでも、『アニメーション』『ページ内』『ページ読み込み』という3つが示されています。1秒はページ内では遅いかも知れませんが、画面の読み込み時間であれば快適な範囲内です。
20ミリ秒はページ内応答時間であれば十分高速ですが、『アニメーション』のフレームという観点では、60フレーム/秒に満たないため、若干の遅延を感じる可能性があります。

一言に画面の応答時間は『1秒』を基準にしましょう。のようなパフォーマンスの設定方法は、UXを損なう可能性があるため、『応答時間』だけを絶対の基準にするのは危険だなと感じました。

1秒あたりから違和感を感じ始める

『応答時間』を絶対の基準にするのは危険とは言いつつも、2つの指標を見る限り、1秒あたりでユーザーは何らかの違和感を感じ始めるようです。(もちろん場面やタスクによります。)

10秒を超えると作業なんてしていられない

そして、もう一つ重要な基準になるのは、10秒というラインです。
10秒を超えてしまえば、ユーザーの集中力は完全に切れて、イライラして作業どころではない。というのは2つの指標で共通しています。
(恐らくは私は10秒も待てません。6~7秒くらいでイライラしていると思います。)

まとめ


  • 『応答時間』の秒数だけを絶対の基準にしてパフォーマンスを設定するのは危険。
  • 『応答時間』とその時間がかかっている場面やタスクを合わせて考えるのが重要
  • 『応答時間』だけに限って言えば、1秒でユーザーは違和感を感じ始める。
  • 『応答時間』だけに限って言えば、10秒を超えればユーザーはいなくなる。

参考・引用資料


コードリーディングが捗る"github1s.com"

はじめに


今日はTLで流れてきた"github1s.com"が気になったので、調べてみたら意外と使い勝手が良かったのでまとめみました。

流れてきたTL


TLが流れたときは、正直最初は何がどうなっているのかよく分かりませんでしたが、じっくり見てみてるとなかなかすごい。

リポジトリを開いたら、https://github1s.com/atEaE/hogehogegithubの後ろに1sを追加するだけです。
たったそれだけで、ブラウザ上のVSCodeからソースコードを確認することができます。

github1s自体はOSSで公開されています。

使用感


1. コードリーディングが捗る

今までOSSのソースコードを確認するときには、Github上だけではどうしてもコードが読みづらかったので、.zipをダウンロードしたり、cloneしたりしていました。
ところがgithub1sでは1sを付け加えるだけで、Github上で見るよりも遥かにソースコードが読みやすいです。
開発者の方々ありがとう!

2. タグやブランチ別のソースコードも確認可能

タグ別のソースコードや、ブランチ別のソースコードも同じ要領で確認することができます。

タグ別はhttps://github.com/atEaE/hogehoge/tree/v1.0.0
ブランチ別はhttps://github.com/atEaE/hogehoge/tree/developのようになります。
後は同じように1sを付けるだけで、ソースコードの確認ができます。

3. OAuth Tokenを登録すれば自身のPrivateリポジトリも確認可能

Githubの OAuth Tokenを使用していない場合は、Privateリポジトリのソースコードを確認することができません。
また、Github APIの使用上は1時間あたりのリクエスト数に限りがあります。
(未認証時は1時間あたり最大60リクエストまで可能。認証時は、1時間あたり最大5,000リクエストまで可能。) Privateのリポジトリの確認がしたい場合や、リクエスト制限を緩和したい場合は、OAuth Tokenを登録しましょう。

f:id:EaE:20210210215631p:plain

Tokenは↑の通り、メニューから登録することができます。

VSCodeとの差異


1. フォルダ横断検索ができない

VSCodeと同じような見た目をしていますが、完全に同じ機能を有しているわけではありません。
まず、フォルダ横断した検索を実行することができません。
あくまで、アクティブファイルに対しての検索しかできません。

f:id:EaE:20210210211819p:plain (アクティブファイルはEditor上で開いているファイルのことです。)

2. 一部の言語はコードジャンプが機能しない。

C#, golang, Typescript, javascriptでリポジトリを確認しましたが、C#, golangではコードジャンプが機能しませんでした。
Typescript, Javascriptに関しては、カーソルをHoverさせることで定義の確認まですることができました。

3. 基本的にファイルの編集はできない。

ファイルの編集は基本的にはできません。
『New File』から新規タブを開いた場合は、例外的に編集を行うことができますが、新規タブで作成したファイルは保存できないので、メモ書き程度なのかなと。思いました。

まとめ


  • コードリーディングが捗る。
  • 欲を言えば、フォルダの横断検索とコードジャンプが使えるといい。