isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

Webが乗り越えるべき9個の課題

一人のWebアプリケーション開発者として、Webの世界に足りないと感じるものをまとめてみた。

Webの仕様の問題と業界の問題を区別せずに列挙した。

 

  1. 広告モデルからの脱却
  2. ネイティブUIを呼び出せるようにして
  3. お行儀の悪いアナリティクス用のJSを排除して
  4. もうサードパーティークッキーは禁止すれば?
  5. プライバシーに対する意思表示をセマンティックに
  6. ペアレンタルコントロールDNSを使うのはおかしくない?
  7. プロキシが流行ってほしい
  8. strict HTMLヘッダーの提案
  9. テキストエディタ

広告モデルからの脱却

今の所、コンテンツ提供者の収入源は広告だけだ。だから、僕は良心的理由でAdBlockなどを入れていない。しかし、はっきり言って広告はウザい。広告は画面を大きく占有する。広告は過激な色使いでチカチカしてくる。目立ちたいからだ。こっちは興味ないのに。

もっと簡単にコンテンツに課金する手段が必要だ。

コンテンツ課金の辛い点は、お金を払うことではない。サイト毎に自分の個人情報を入力することの不安と手間だ。

よって、ブラウザにその機能をもたせたい。自分の決済情報を入力しておけば、ワンタップでコンテンツを購入できる仕組みがあればいいと思う。当然こちらの個人情報は渡らないようにする。

 

https://jp.techcrunch.com/2020/03/26/2020-03-25-mozilla-scroll-partnership/

Scrollというサービスがある。もしかしたらこれが来るかもしれない。

 

ネイティブUI

Webアプリの最大の弱点はUIコンポーネントだ。

僕はAndroidアプリやiPhoneアプリも開発している。

AndroidiOSの標準コンポーネントを使えばサクサク動く。

それに対してWebの画面は常にもっさりしている。

タップやスクロールの判定がおかしくてイライラする。

所詮、HTMLとCSSは論文交換プラットフォームだ。

HTMLの表現力はネイティブの操作性には勝てない。

 

参考

 

blog.isyumi.net

 

 

少なくともGoogleFacebookTwitterですら、ネイティブアプリと同レベルの触り心地のWebアプリを作れていないのだから、これは絶対ムリなんだと思う。

 

よって、スマホのブラウザはJSからネイティブUIコンポーネントを起動できるようにするべきだ。

 

アナリティクス

広告業者やアクセス解析業者はサイトオーナーにトラッキングタグ的なものを提供している。

このスクリプトのお行儀が悪すぎる。

Chrome DevToolsのネットワークタブを見るとわかる。ページロード時にスクリプトがいっぱい無駄な通信を発生させている。

僕はマジでキレてる。

PageSpeed Insightsで95点のサイトにトラッキングタグを入れるだけで60点台になったりする。

Googleが提供するタグですらPageSpeed Insightsを10点近く下げる。

もう、トラッキングタグをJSで実装するのを禁止してほしい。

サーバーサイドでやればいいじゃん。

WebサーバーがAnalyticsサーバーにAPI経由でアクセスイベントを送信すればいい。

ちなみに、ブラウザにネイティブの機能としてそれを入れようと話し合われている。

早く何とかしてほしい。

https://github.com/WICG/conversion-measurement-api

 

サードパーティクッキー

ITPが始まった時、絶対これは無理筋だろうと思った。案の定カヲスなことになってきた。

度々抜け道が見つかるからだ。ITPの挙動自体が状態を持つ。それを悪用すればITPを使ってユーザーをトラッキングできてしまう。その度にAppleが複雑なルールを導入して対応する。イタチごっこになっている。

もう、まともな良心を持ったサイトオーナーはサイトをまたいだトラッキングは諦めていると思う。わざわざ頑張ってAppleの規制を迂回するのも面倒だ。そもそも、サイトをまたいでユーザーの行動を突き合わせてターゲティングするのは人権侵害感が出てきている。善人でありたい。

”インテリジェントな”トラッキング防止なんてできなかったんだ。

この際、サードパーティークッキーは全部禁止したらいい。

 

プライバシーに対する意思表示

最近、西洋の会社のサイトに行くといちいちCookieを使っていいか聞かれてウザい。

どの程度Cookieを使っていいかなんて考えたらわかるだろうと思ってる。

つまらない保険を掛けるために僕のアテンションを奪わないでほしい。

そこで、そのJSONフォーマットを定めたい。

サイトはJSONに個人情報の使い方を明示してそのURLをmetaタグに書く。

ユーザーは各自自分のブラウザに自分のプライバシーに対する考え方を表明しておく。

訪れたサイトがその意思表明に合致していたらそのまま閲覧が出来る。

合致していなかったら警告が出たりする。

それで済む話だ。

 

ペアレンタルコントロール

ペアレンタルコントロールDNSのレイヤーを使うのはおかしくないか?

僕はあまりペアレンタルコントロールという物自体が好ましいと思えない。しかし残念ながら、これがないと色んな話が政治的にまとまりづらい。

例えば、ペアレンタルコントロールができないと学校でノートPCを配布できないだろう。

さて、最近はペアレンタルコントロールDNSのレイヤーを使うことが多いようだ。

確かに、アダルトサイトのDNSを引けないようにしておけばサイトの閲覧を阻止できる。

しかし、これは情報汚染だと思う。

アダルトサイトのIPアドレスが何番であるかというのは、あくまで事実かどうかの問題であって、善とか悪とかを持ち込んでいい場所ではない。それはもう一個上のレイヤーでやることだ。この辺を区別しないのはポル・ポトと同レベルだと思う。

とりあえずDNSをそういうことに使うのをやめてほしい。

じゃあどうすればいいのか、プロキシだと思う。

 

プロキシ

僕はフォワードプロキシに絶大なる未来を感じている。フォワードプロキシこそがWebの未来だと思う。

 

 

blog.isyumi.net

 

 

フォワードプロキシを使うことが当たり前になるといいと思う。

画像圧縮などが出来るようになって便利なはずだ。

これは、一時期問題になった回線事業者による勝手な画像圧縮や政府による検閲とは違う。

あくまで、個人が自分の意思で業者を選んで使うものだ。

 

しかし、HTTPSに対するまともなプロキシプロトコルが存在しない。

HTTPSのプロキシプロトコルを定めるべきだ。

 

strict HTML

JSにuse strictという機能がある。

これのHTML版を作って欲しい。

ブラウザは、少々おかしなHTMLでも正しく解釈してくれる。

閉じタグを書き忘れたりしても、なんとなくリカバリしてくれる。だがその為に余計な処理能力を使っているはずだ(僕はブラウザの実装には詳しくないので想像だ)。

ところで、僕は大体ReactのSSRでHTMLを作って出力している。ReactのSSRは綺麗なHTMLを出力する。閉じタグつけ忘れなんて無いはずだ。綺麗なHTMLを出力している開発者に何のご褒美もないのはおかしい。

そこで、HTTPヘッダーにuse-strict-htmlと書いたら、ブラウザは綺麗なHTMLが来ることを期待するという仕様を作れないか。

そうすれば、ブラウザはより高速にHTMLをパース出来る。

HTMLのバイナリフォーマットを作ってもいいかもしれない。

 

 

テキストエディタ

GUIでテキストを編集できる系のWebアプリで使いやすいものを見たことがない。

はてなブログGoogle DocsTwitterの入力画面も。

ちなみに、UbuntuChromeTwitterに日本語入力しようとするとバグる。

唯一心を許しているのはStackEditという2ペインのマークダウンエディタだ。

textareaタグを頑張ってカスタムしてテキストエディタを作るのはもう限界だ。

なんとかしてほしい。

感銘を受けた本

せっかくなので僕が感銘を受けた本をいくつかあげたいと思う。
婚活はともかく、好きな本の話はわかりやすく自己紹介できるから良い。
IT系の本は除いた。内輪ネタだからね。

 

 

もっと動的なPictureタグがほしい。

この仕様、1枚の画像をgulpで数種類のサイズ・フォーマットに変換してS3などに置く運用を想定していたと思う。

しかし、今では画像変換機能付きのCDNを通すのが当たり前になった。

つまり、Pictureタグの中で対応している画像サイズを明示する必要がまったくないのだ。また、画像フォーマットごとにURLを指定する意味もない。大体拡張子をみてそのとおりに変換するからだ。

よって、もっと動的な動きが出来るPictureタグが必要だ。オリジナル画像の縦横サイズと、CDNが対応している画像形式だけを指定すれば、動的にURLを変更してくれればいい。

 

タグ

<picture
 src="https://img.example.com/cat.jpg.$format?width=$width&height=$height"
    format="jpg,png,gif,webp,bmp"
    original-width="1200"
    original-height="800"
    size="50w" />

 

 

コレでよくね?

抽象度を意識するとプログラミングが上達する。

抽象度は上げて落とせ。

 

僕は正規表現を使わない。

僕はシステム開発の中で正規表現をほとんど使わない。

外部システムから取得したデータを取り込むときに渋々使うくらいだ。

文字列置換もあまりしない。

 

プログラミングが下手な人が作ったシステムは正規表現や文字列置換が多い気がする。そこで、なぜその人は正規表現を使わなければいけなくなってしまったのか考えることで上達のヒントとしたい。

下手な人の例

例えば、ECサイトを作っているとしよう。商品には値段がある。値札は¥マークと三桁ごとのコンマが振られてた文字列だ。

さて、商品の「カートに入れる」ボタンを押すと「合計金額」が加算される機能を作るとする。

下手な人は、画面の中にある三桁ごとのコンマが振られた文字列を何とかしてintにして変換して、それを足し算して、その結果に3桁ごとのコンマを振って、それを画面に表示しようとする。

それに対して上手な人はそもそも最初からint型で値段と現在の合計金額を引きまわしているので、そんなことをしなくていい。

 

これがシステムの中に出現する正規表現の数の差になる。そして、システム開発のうまさの差である。

抽象度に対する意識の差

プログラミングが上手な人と下手な人の間には、抽象度に対する意識の差がある。

上手い人は、極力抽象度を高く保ち続けようとする。画面に表示する最後の最後の瞬間に抽象度を落とすことでビューに変換する。

 

逆に下手な人は抽象度を意識できていないのだろう。そもそも、抽象であるデータ構造と具体である画面を分けて考えられないのかもしれない。

抽象度の高いデータとは

例を挙げよう。

Stringの"1,800"よりintの1800のほうが抽象度が高い。

JSTUTCならUTCのほうが抽象度が高い。

誕生日・現在時刻・年齢や定価・税率・税込価格のように3値の内2値が定まれば第3値が導出できるものは、どれか一つを省いた方が抽象度が高い。

不正値が混じっているかもしれないデータと不正値が混じっていないことを確認したデータなら確認したデータの方が抽象度が高い。

抽象度の3原理

抽象度の高低については、この三原理がいえる。

  • 抽象度の高いデータの方が扱いやすい
  • 抽象度の高いデータを抽象度の低い表現に落とすのは簡単
  • 抽象度の低い表現を抽象度の高いデータに変換するのは難しい

いいコードを書くための原則

ということは、プログラミングをするときはこうするべきだ。

処理の入口で最も抽象度の高いデータに変換する。

すべてのビジネスロジックは抽象度の高いデータを引数に、抽象度の高いデータを返す関数として書く。

処理の出口は、抽象度の高いデータのみを引数にして任意の出力をするように実装する。

 

これをシステム開発全体で意識することができたらプログラミングは上達すると思う。

尺取り法の最強ライブラリを作った。

僕は水色コーダーでありながら尺取り法がとても苦手である。
ミスりやすい。
そこでこの度、尺取り法の最強ライブラリを作った。

 

尺取り法の難しさ

実装上の注意として2つの点が挙げられる。

  1. どの尺にも入れない要素がいる
  2. LeftがRightを追い越す時の処理。

 

1.どの尺にも入れない要素がいる

例えば和が100以下の尺を数え上げる問題で、200の要素が混じっていたりすると、その前後の処理が複雑になる

2.LeftがRightを追い越す時の処理

LeftとRightを追い越す時に、Rightを追従させなければいけない。この時の処理を忘れがちである。

時には1と2が同時に起きることもある。非常に辛い

 

解決

尺が存在する区間を列挙

まず、単体で条件を満たす要素を列挙する。
そして、尺が存在する区間を列挙する。
たとえばこの数列から和が8以下の尺を列挙するとする

{ 1, 2, 8, 10, 3, 4, 6}

単体で存在可能な要素は{1, 2, 8, 3, 4, 6}なので、
尺が存在する区間は{1, 2, 8}と{3, 4, 6}である。
この2つの区間をそれぞれ処理すればいい。
この時点で、1のどの尺にも入れない要素がある問題はクリアできた。

尺が存在する区間の始まりをハンドリングする

次に、2の問題はライブラリが区間の開始イベントを送ってくれると解決する。
区間が始まるタイミングは3つだ

  • 配列の最初の要素
  • 自分の左がどの尺にも入れない要素
  • 左が右を追い越した時

プログラマは、区間の開始を知らされた時に尺を初期化すればいい。

 

実装

プログラマは以下の6個のイベントハンドラを処理すればいい。

  • この要素は単体で条件を満たしているか?
  • 左を固定して尺を初期化しろ
  • 右に1個伸ばせるか?
  • 右に1個伸びろ
  • 左を1個捨てろ
  • 区間が確定した

 

ライブラリ

void tow_pointer(int n,
function<bool(int left)> check_begin,
function<void(int left)> begin,
function<bool(int right)> check_right,
function<void(int right)> push_right,
function<void(int left)> pop_left,
function<void(int left, int right)> dead_end
) {

auto go = [&](P range) {
int right = -1;
for (int left = range.first; left <= range.second; left++) {
if (right < left) {
right = left;
begin(left);
}
while (right + 1 <= range.second && check_right(right + 1)) {
right++;
push_right(right);
}
dead_end(left, right);
pop_left(left);
}
};


auto get_ranges = [&] {
vector<bool> enable_items(n);
rep(i, n) enable_items[i] = check_begin(i);

auto start = [&](int i) {
if (i == 0) {
return enable_items[i] == true;
}
if (!enable_items[i - 1] && enable_items[i]) {
return true;
}
return false;
};

auto end = [&](int i) {

if (i == n - 1) {
return enable_items[i] == true;
}

if (enable_items[i] && !enable_items[i + 1]) {
return true;
}

return false;
};

vector<P> ans;
P p;
rep(i, n) {
if (start(i)) {
p = P(i, -1);
}
if (end(i)) {
p.second = i;
ans.push_back(p);
}
}
return ans;
};


vector<P> ranges = get_ranges();
for (P range: ranges) go(range);
}

使い方

例えばこんな感じで使える

https://atcoder.jp/contests/abc032/tasks/abc032_c

 

int main() {
int n;
ll k;
cin >> n >> k;

vector<ll> numbers(n);
rep(i, n) cin >> numbers[i];

if (find(numbers.begin(), numbers.end(), 0ll) != numbers.end()) {
cout << n << endl;
ret();
}
ll sum = 0;

// この要素は単体で条件を満たすか?
auto check_begin = [&](int left) {
return numbers[left] <= k;
};

// leftを左として尺を初期化しろ
auto begin = [&](int left) {
sum = numbers[left];
};

// 右に1個伸びれるか確認しろ
// 次の「1個右の要素は」right
auto check_right = [&](int right) {
return sum * numbers[right] <= k;
};

// 右に1このびろ
// 次の「1個右の要素は」right
auto push_right = [&](int right) {
sum *= numbers[right];
};

// 左を1個捨てろ
// 捨てる左の要素はleft
auto pop_left = [&](int left) {
assert(sum % numbers[left] == 0);
sum /= numbers[left];
};

ll ans = 0;

// 尺が伸びれるところまで伸びた
auto dead_end = [&](int left, int right) {
ll now = right - left + 1;
cmax(ans, now);
};


tow_pointer(
n,
check_begin,
begin,
check_right,
push_right,
pop_left,
dead_end
);
cout << ans << endl;

}

このあたりを見てほしい。

https://atcoder.jp/contests/abc032/submissions/11171624

https://atcoder.jp/contests/abc038/submissions/11171673

https://atcoder.jp/contests/arc022/submissions/11171696

https://atcoder.jp/contests/abc098/submissions/11171718

 

サンプル自動生成力があれば時々青問題が解ける

昨日のAtCoder Grand Contest 043でB問題を解いて2完できた。うれしい。
水色コーダーには望外で僥倖だ。

https://atcoder.jp/contests/agc043/tasks/agc043_b

僕は稀にこの手の青難易度問題を解いてしまうことがある。そのテクニックを紹介しよう。

やること

  • 愚直解を書く
  • テストケースを自動生成するコードを書く
  • For文で小さいケースから順番に全部試す
  • プリントする

昨日のB問題は8桁くらいを一瞬でprintできる。

この手の問題は絶対に簡単な規則性があるので、ずーと眺めていたら何か見つかるかもしれない。

昨日の問題は、「2段目以降は、nが偶数ならトーナメントにして解いても同じ結果になる」ということに気づいた。愚直解なら1段につき1要素しか減らないのでo(n^2)になってTLEだが、トーナメントにして1段を半分にしていいならo(log n)になるのでACできる。ということは、あとはnが奇数の時だけ考えればいい。奇数の時も簡単だ。1段を愚直解すれば長さが偶数になる。

 

// 一段減らす
vector<int> sub(vector<int> &v) {
if (v.size() == 1) return v;
vector<int> v2(v.size() - 1);
rep(i, v.size() - 1) {
v2[i] = abs(v[i] - v[i + 1]);
}
return v2;
}

// 半分にする
vector<int> tournament(vector<int> &v) {
vector<int> next;
for (int i = 0; i < v.size(); i += 2) {
int a = abs(v[i] - v[i + 1]);
next.push_back(a);
}
return next;
}

// 偶数なら半分にする
// 奇数なら一段減らしてから半分にする
int solv(vector<int> v) {
while (v.size() != 1) {
if (v.size() % 2 == 0) {
v = tournament(v);
} else {
v = sub(v);
}
}
return v.front();
}

 

提出前にすること

「多分解けたな」と思ったら、せっかくなのでさっき作ったテストケース自動生成コードを使って確認してほしい。小さいサンプルで、愚直解と同じ結果になるか試すのだ。結構地味なコーナーケースを検出することがある。大体こういう問題はnが極端に小さい時に例外がある。昨日の問題なら2231の時だけこの方法ではうまくいかないことが検出できた。小細工をして通した。逆に言えば、nが小さいケースを全部通せたらだいたいACできる。

その他

愚直解も実装が複雑な問題であれば、TLEになることが分かっていても愚直解をジャッジに投げてほしい。その勇気も必要だ。ペナルティはたかが5分だ。間違った愚直解で時間を失うよりましだ。

 

おススメ

このブログに感化された緑・水色コーダーの方は、ぜひこのやり方でこの問題を解いてみてほしい。
頭で考えるとめちゃくちゃ難しい問題だが、小さいケースからprintしていけば超簡単だ。
https://atcoder.jp/contests/abc059/tasks/arc072_b

 

Web広告系のトラッキングタグは何でこんなに遅いんだ。

お仕事でECサイトを作った。
僕はWebページの高速化が得意だ。
広告系のトラッカーを入れるまではPageSpeed Insightsが97点だった。

 

それから言われたサードパーティの広告系のタグを入れた。
FacebookのトラッキングタグにYahoo広告のトラッキングタグに......その他いろいろ。

 

それらを入れただけでPageSpeed Insightsが80点を切った。
ため息が出る。
もう少し気を使ってくれ。
Google Analyticsくらいは良い奴だと思っていたのに。

 

こういうのは全部WebWorkerに持っていく流れにならないかな。

 

もしくは、どうせサードパーティCookieは消されていくんだし、サーバーサイドからログを送信する方向になってほしい。サーバーからIPとUAとURLを送れば大体事足りるでしょう。