技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

Chromeの拡張機能を使用した文字列置換

以前の記事で、URLからJavaScriptを読みこむことでブラウザに表示される文字の変換を試みました。
しかし、URLから読み込ませる方法だと、大量の文字列置換はできません。
 
そこで、今回はChrome拡張機能を使用した文字列置換を試みました。
以下、サンプルコードです。
 
manifest.jsonは、「https://developer.mozilla.org/ja/docs/Web/JavaScript」にアクセスした際に「content.js」が実行される記述としています。
最小限の記述であるため、実際に配布する際には説明を入れたりアイコンをつけたりすると良いでしょう。
 
【構成】
test┳manifest.json
  ┗content.js
 
【サンプルコード】
・manifest.json
{
 "name": "test",
 "version": "1.0.0",
 "manifest_version": 2,
 "content_scripts": [{
  "matches": ["https://developer.mozilla.org/ja/docs/Web/JavaScript"],
  "js": [
   "content.js"
  ]
 }]
}

 
・content.js
document.body.innerHTML=document.body.innerHTML.replace(/JavaScript/g,'[JavaScript]');
document.body.innerHTML=document.body.innerHTML.replace(/ECMAScript/g,'[ECMAScript]');
 
【動作確認】
1.Chromeを開き、下記から「拡張機能の管理」を選択

f:id:akira2kun:20210302230333j:plain

 

2.「デベロッパーモード」をONにし、「パッケージ化されていない拡張機能を読み込む」を選択

f:id:akira2kun:20210302230354j:plain

 

3.サンプルコードが存在するフォルダを選択

f:id:akira2kun:20210302230420j:plain

 

4.manifest.jsonに記述したサイトにアクセスすると、自動で文字列置換が行われる

f:id:akira2kun:20210302230439j:plain

 

5.取り込んだ拡張機能の削除は、「Chromeから削除」を選択することで行う

f:id:akira2kun:20210302230504j:plain

JavaScript:thisの挙動と用途

JavaScriptの初心者を悩ます文法の一つとして、thisが挙げられます。
thisが何を指しているのか、どのような場合に使われるのか、なかなかイメージが難しいと思います。
 
この記事では、その疑問について答えていきたいと思います。
 
今回の記事では、サンプルコードの実行はNode.jsで行います。
また、非Strictモード('use strict'を宣言したりexport句でStrictモードに変更したりしない場合)を前提とします。
(Strictモードの場合は、thisによりグローバルオブジェクトを参照しようとした場合にundefinedになります)
 
----
 
1.thisキーワードの説明
「this」の説明の前に、JavaScriptとオブジェクトの関係について簡単に説明します。
JavaScriptは、必ずオブジェクトの中で実行する必要があります。
実行環境(ブラウザやNode.js)は「グローバルオブジェクト」と呼ばれるオブジェクトを用意しており、自分でオブジェクトを定義しない場合はグローバルオブジェクトから各々の処理が実行されています。
(ブラウザの場合のグローバルオブジェクトは「window」、Node.jsの場合のグローバルオブジェクトは「global」となります)
 
「this」キーワードは、記述箇所を動かしているオブジェクトを参照します。
自分で定義したオブジェクトから記述箇所を呼び出している場合はそのオブジェクト、そうではない場合はグローバルオブジェクトを返します。
 
【サンプルコード】
function func1() {
 console.log(this); // 関数を呼び出しているオブジェクトを参照
}
func1(); // グローバルオブジェクトの関数を実行
let obj = {}; // 独自のオブジェクト定義
obj.func = func1; // 独自のオブジェクトに関数を定義
obj.func(); // 独自のオブジェクトの関数を実行
 
【実行結果】
・1回目の関数呼び出し
Object [global] {
 global: [Circular],
 clearInterval: [Function: clearInterval],
 clearTimeout: [Function: clearTimeout],
 setInterval: [Function: setInterval],
 setTimeout: [Function: setTimeout] {
  [Symbol(nodejs.util.promisify.custom)]: [Function]
 },
 queueMicrotask: [Function: queueMicrotask],
 clearImmediate: [Function: clearImmediate],
 setImmediate: [Function: setImmediate] {
  [Symbol(nodejs.util.promisify.custom)]: [Function]
 }
}
 
・2回目の関数呼び出し
{ func: [Function: func1] }
 
2.callやapplyを用いて関数呼び出しした場合のthisの参照先
関数をメソッドチェーンで「call」や「apply」と繋ぐことで、呼び出される関数の中でthisが参照するオブジェクトを指定することができます。
オブジェクトの指定は「call」や「apply」の第一引数で行います。
 
また、「call」や「apply」は第二引数以降も指定が可能です。
第二引数以降では、関数呼び出しの際の引数を指定することができ、呼び出される関数の中では「arguments」キーワードでそれを受け取ることができます。
「call」は第二引数、第三引数、第四引数…といった具合に変数を一つずつ定義し、「apply」は第二引数に配列形式でまとめて変数を定義します。
 
【サンプルコード1】
function func1() {
 console.log(this);
}
let obj = {hoge: "hoge"};
func1.call(obj); // this=objと結びつけることができる。
func1.apply(obj); // 同上
 
【実行結果1】
・1回目の関数呼び出し
{ hoge: 'hoge' }
 
・2回目の関数呼び出し
{ hoge: 'hoge' }
 
【サンプルコード2】
function func1() {
 console.log(this);
 for (var i = 0; i < arguments.length; i++) {
  console.log(arguments[i]);
 };
}
let obj = {hoge: "hoge"};
func1.call(obj, "Hello", "World!"); // 引数を1つずつ指定
func1.apply(obj, ["Hello", "World!"]); // 引数を1つの配列で指定
 
【実行結果2】
・1回目の関数呼び出し
{ hoge: 'hoge' }
Hello
World!
 
・2回目の関数呼び出し
{ hoge: 'hoge' }
Hello
World!
 
3.bindを用いたthisの参照先変更
関数をメソッドチェーンで「bind」と繋ぐことで、thisの参照先を強制的に結びつけた新たな関数を定義できます。
結びつけるオブジェクトは第一引数で定義します。
 
【サンプルコード】
function func1() {
 console.log(this);
}
let obj = {hoge: "hoge"};
let func2 = func1.bind(obj); // thisを強制的に結び付けて新たな関数を定義
func2();
 
【実行結果】
{ hoge: 'hoge' }
 
4.アロー関数で関数定義した場合のthisの参照先
アロー関数で関数を定義した場合は、thisの参照先は固定になります。
関数を定義した時点のオブジェクトを指すようになります。
 
【サンプルコード】
let func1 = () => { // アロー演算子で関数を定義
 console.log(this); // この場合は、定義時点の呼び出し元オブジェクトでthisを決定
}
func1(); // グローバルオブジェクトから呼び出してもthisは空のオブジェクトを指す
let obj = {};
obj.func = func1;
obj.func(); // 独自のオブジェクトから呼び出してもthisは空のオブジェクトを指す
 
【実行結果】
・1回目の関数呼び出し
{}
 
・2回目の関数呼び出し
{}
 
----
 
thisは、どのオブジェクトから呼び出されたかによって挙動を変えたい時に使用することが多いです。
ポリモーフィズムを実現するために使用する、と言った方がわかりやすいかもしれません。
また、今回の例では取り上げていませんが、関数を定義したオブジェクトを渡せばコールバック処理も可能です。
 
以下のサンプルコードは、呼び出したオブジェクトの名前を参照する例であり、どのオブジェクトから呼び出したかによって挙動を変えることができています。
 
【サンプルコード】
function func1() {
 console.log(this.name); // 呼び出したオブジェクト毎で違う名前を表示
}
let obj1 = {name: "hoge"};
let obj2 = {name: "fuga"};
func1.call(obj1);
func1.call(obj2);
 
【実行結果】
・1回目の関数呼び出し
hoge
 
・2回目の関数呼び出し
fuga

JavaScript:Strictモードとは

JavaScriptには「Strictモード」と呼ばれるモードが用意されています。
Strictモードにすることで、バグに気付かずに実行が継続されるのを防ぎやすくなり、高速化することもあります。
 
詳しくはMozillaのページ(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode)に書かれていますが、この記事では簡単に紹介したいと思います。
 
----
 
Strictモードに設定するためには、以下の2つの方法があります。
(export句を使用した方法もありますが、この記事では割愛します)
 
1.スクリプト全体で定義
スクリプトの最初に「'use strict';」を記述することで、そのスクリプト全体がStrictモードになります。
 
・サンプルコード
'use strict'; // スクリプトの最初で定義、スクリプト全体がstrictモード
console.log("Hello world!");
 
2.関数全体で定義
関数の最初に「'use strict';」を記述することで、その関数全体がStrictモードになります。
 
・サンプルコード
function hoge() {
 'use strict'; // 関数の最初で定義、関数全体がstrictモード
 console.log("Hello world!");
}
// 関数の外はstrictモードではない
hoge();
 
----
 
Strictモードに設定することで、例えば以下のような挙動変更が行われます。
 
1.バグになりやすい誤りがある場合に異常終了させる
例えば、定義済みの変数を参照しようとして変数名の記述を誤った場合、Strictモードの場合は異常終了させることができます。
 
・サンプルコード(非Strictモード)
// 'use strict';
let hoge = "definition";
hage = "Hello world!";
console.log(hoge);
 
・実行結果(非Strictモード)
C:\tmp>node hello.js
definition

C:\tmp>
 
・サンプルコード(Strictモード)
'use strict';
let hoge = "definition";
hage = "Hello world!";
console.log(hoge);
 
・実行結果(Strictモード)
C:\tmp>node hello.js
C:\tmp\hello.js:3
hage = "Hello world!";
   ^

ReferenceError: hage is not defined
  at Object.<anonymous> (C:\tmp\hello.js:3:6)
  at Module._compile (internal/modules/cjs/loader.js:1137:30)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)
  at Module.load (internal/modules/cjs/loader.js:985:32)
  at Function.Module._load (internal/modules/cjs/loader.js:878:14)
  at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
  at internal/main/run_main_module.js:17:47

C:\tmp>
 
2.セキュリティーの強化
JavaScriptでは、必ずオブジェクトを用意する必要があり、自分でオブジェクトを定義しない場合は「グローバルオブジェクト」と呼ばれるブラウザや実行環境が用意したオブジェクトが使用されます。
また、キーワード「this」を使用することで、記述箇所を動かしているオブジェクトの情報を取得することができます。
「this」によりグローバルオブジェクトを参照された場合、セキュリティー上の問題が発生する可能性があるため、Strictモードではグローバルオブジェクトが参照されるようなケースで「this」キーワードは「undefined」を返すようになります。
 
・サンプルコード(非Strictモード)
//'use strict';
function hoge() {
 console.log(this); // 関数を呼び出しているオブジェクトを参照
}
hoge(); // グローバルオブジェクトの関数を実行
let hello = {}; // 独自のオブジェクト定義
hello.hoge = hoge; // 独自のオブジェクトに関数を定義
hello.hoge(); // 独自のオブジェクトの関数を実行
 
・実行結果(非Strictモード)
C:\tmp>node hello.js
Object [global] {
 global: [Circular],
 clearInterval: [Function: clearInterval],
 clearTimeout: [Function: clearTimeout],
 setInterval: [Function: setInterval],
 setTimeout: [Function: setTimeout] {
  [Symbol(nodejs.util.promisify.custom)]: [Function]
 },
 queueMicrotask: [Function: queueMicrotask],
 clearImmediate: [Function: clearImmediate],
 setImmediate: [Function: setImmediate] {
  [Symbol(nodejs.util.promisify.custom)]: [Function]
 }
}
{ hoge: [Function: hoge] }

C:\tmp>
 
・サンプルコード(Strictモード)
'use strict';
function hoge() {
 console.log(this); // 関数を呼び出しているオブジェクトを参照
}
hoge(); // グローバルオブジェクトの関数を実行
let hello = {}; // 独自のオブジェクト定義
hello.hoge = hoge; // 独自のオブジェクトに関数を定義
hello.hoge(); // 独自のオブジェクトの関数を実行
 
・実行結果(Strictモード)
C:\tmp>node hello.js
undefined
{ hoge: [Function: hoge] }

C:\tmp>
 
3.将来(最新)のJavaScriptの仕様に対応する
例えば、キーワード「private」は、2021/02/21現在でいくつかのブラウザやNode.jsでは使用可能です。
非Strictモードではこのキーワードを変数名として使用できますが、Strictモードではエラーにできます。
 
・サンプルコード(非Strictモード)
// 'use strict';
let private = "Hello world!";
console.log(private);
 
・実行結果(非Strictモード)
C:\tmp>node hello.js
Hello world!

C:\tmp>
 
・サンプルコード(Strictモード)
'use strict';
let private = "Hello world!";
console.log(private);
 
・実行結果(Strictモード)
C:\tmp>node hello.js
C:\tmp\hello.js:2
let private = "Hello world!";
^^^

SyntaxError: Unexpected strict mode reserved word
  at wrapSafe (internal/modules/cjs/loader.js:1053:16)
  at Module._compile (internal/modules/cjs/loader.js:1101:27)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:1157:10)
  at Module.load (internal/modules/cjs/loader.js:985:32)
  at Function.Module._load (internal/modules/cjs/loader.js:878:14)
  at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
  at internal/main/run_main_module.js:17:47

C:\tmp>

ブラウザで表示される文字列をJavaScriptのURLを読ませて置換する

ブラウザで表示される文字列について、お気に入りからJavaScriptを読みこませることで置換することができます。
例えば、強調表示したいような場合にこれを活用できます。
 
例として、mozillaJavaScriptのページを強調表示してみましょう。
https://developer.mozilla.org/ja/docs/Web/JavaScript
 
今回の例では、「JavaScript」と「ECMAScript」を強調表示します。
 
1.お気に入りにJavaScriptを登録する
Chromeの場合は以下のような手順になります。
 
・ブックマークマネージャーを開く

f:id:akira2kun:20210214180437j:plain
 
 
・右クリックで新しいブックマークを作成する

f:id:akira2kun:20210214180525j:plain
 
・ブックマークのURLにJavaScriptを記述する

f:id:akira2kun:20210214180541j:plain
javascript:(function(){document.body.innerHTML=document.body.innerHTML.replace(/JavaScript/g,'[JavaScript]');document.body.innerHTML=document.body.innerHTML.replace(/ECMAScript/g,'[ECMAScript]');})()
 

2.任意のページでブックマークを開く
今回の例では、このようなページが

f:id:akira2kun:20210214180622j:plain

 
ブックマークを開くことでこのように強調表示されます。

f:id:akira2kun:20210214180647j:plain

 

----
 
このように手軽にブラウザの表示の文字列置換が可能なのですが、この方法では大量の文字列置換ができないことに注意する必要があります。
少なくとも、筆者の環境(OS:Windows8.1、ビット数:64、メモリ:8GB、プロセッサ:Intel(R) Core(TM) i5-4210U CPU @ 1.70GHz 2.40GHz、Chromeバージョン: 88.0.4324.150)では、1000通りの文字列置換を行うことはできませんでした。
(恐らく、ブラウザ毎に設けられているURLの文字数制限によるものです)
これが問題になるのであれば、Chrome拡張機能を開発する等の別の方法を考えた方が良いです。
 
また、文字列置換によりリンク先が変わってしまうことにも注意が必要です。
(逆に、これを利用して、意図的にリンク先を変えることはできそうです)

Google翻訳を利用した英文コミュニケーション

国内で開発作業に従事している場合でも、国外の技術者とのやりとりが必要になるケースがあります。
特に理系大卒の技術者は英語力に自信が無い場合が多い(理系大卒の新卒時点でのTOEICの平均スコアは400点台)のですが、現在はGoogle翻訳という文明の利器があります。
高校の基礎レベルの文法を理解していれば、Google翻訳を使いつつチャットでのコミュニケーションを成り立たせることができます。
 
通訳を通してコミュニケーションを取るでも良いのですが、通訳を通すとどうしてもスピード感が失われたり認識相違が発生しやすくなったりするので、可能な限り直接やりとりするのが望ましいです。
 
----
 
googleで「日本語 英語」と検索すれば、以下のようにGoogle翻訳の画面を表示することができます。

f:id:akira2kun:20210214172419j:plain

----
 
多くの場合、Google翻訳は日本語を英語に正しく翻訳してくれるのですが、入力する日本語を工夫したり翻訳結果を手直したりした方が望ましい場合があります。
具体的には、以下のような場合に注意する必要があります。
(ここで挙げる以外にも、稀に文法的に誤った翻訳が出力される場合があります。その場合は、翻訳結果を自分で修正する必要があります。)
 
1.主語が省略される場合
日本語は主語が省略される場合が少なくないのですが、英語の場合は原則として主語を省略しません。
そのため、主語のない日本語をGoogle翻訳にかけると、主語が補われます。
 
例えば、
「今日は18:00にリリース作業を行う予定です。」

「We are planning to release it at 18:00 today.」
と翻訳されます。
 
しかし、話の前後で、「We are」(私たち)ではなく「I am」(私)が正しい場合があります。その場合は自分で翻訳結果を修正する必要があります。
また、日本語を入力する時点で主語を省略しないという手段も有効で、例えば
「私は今日は18:00にリリース作業を行う予定です。」
であれば
「I plan to release it at 18:00 today.」
という翻訳になります。
 
2.1文1文で表現を統一したい場合
同じ意味の文章でも、1文1文で異なる表現を用いて翻訳される場合があります。
文章の前後で表現に一貫性を持たせるために、同じ意味の文章なのであれば同じ表現を用いるように翻訳結果を修正した方が、認識相違は少なくなるでしょう。
 
例えば、
「私達は1つのサーバーが止まるケースを対策しなければなりません。私達はトラフィック過多の対策も必要です。」

「We have to deal with the case where one server goes down. We also need measures against excessive traffic.」
と翻訳されます。
 
「対策」という言葉が「deal with」と「measures against」と異なる表現で翻訳されているので、どちらかの表現に統一した方が良いでしょう。
また、日本語を入力する時点で使ってほしい表現を英語で指定する方法もあり、例えば
「私達は1つのサーバーが止まるケースを対策しなければなりません。私達はトラフィック過多をdeal withするのも必要です。」
であれば
「We have to deal with the case where one server goes down. We also need to deal with over-traffic.」
と翻訳されます。
 
----
 
一方、英語から日本語に翻訳する場合は、不自然であったり意味が通じなかったりする翻訳結果になることが少なくないように思います。
 
団体から出している正式な文章であれば問題無いのですが、チャットでやりとりする文章では、くだけた表現が使われたり、文法的に正確ではない表現が使われたりすることがります。
例えば、これは私の趣味(対戦ゲーム)で外国人のプレイヤーとやりとりしている中で実際に使われた文章なのですが、
「I played badly I'll probably lose this one」

「私はひどく遊んだ私はおそらくこれを失うでしょう」
と翻訳されました。
これは「私は悪手を打った。私は恐らくこのゲームで負けるだろう。」と翻訳するのが正しいのですが、ピリオドで文が区切られていないため、また「this one」を正しく翻訳することが難しいため、このような翻訳結果となってしまっています。
ある程度の読解力がないとこのようなケースに対応できないこともあるのですが、技術者の場合は英語のエラーメッセージを読んだり海外のドキュメントを読んだりせざるを得ないことが多いので、実務に従事し続けていれば自然に読解力は身につくと思います。
 
その他、専門用語が出てくる場合や、略語(スラング)が使われる場合(「asap」のようなビジネスで使い古されている表現なら対応できますが、そうではない場合は対応できない場合が多い)は、Google翻訳では対応が難しいです。
この場合は、わからない表現だけWebで調べたり、言葉の意味をチャットで質問したりすると良いでしょう。

サクラエディタの置換機能を使った簡単な独自ソート

サクラエディタの機能として昇順・降順ソートが用意されていますが、それ以外の独自の条件でソートを行いたい場合、置換機能を上手く使うことでソートできる場合があります。
改行コードを一時的に置き換えて1行のファイルにするのと、参照機能で並び替えを行うのがポイントです。
マクロとして保存すれば、繰り返し実行することもできます。
 
本当はjavaC#等のプログラムでソートするべきだとは思いますが、障害対応等で急ぎで作業する必要がある場合に使えるテクニックです。
 
----
 
例えば、下記のCSVファイルについて、2項目目が"END"のレコードを一番最後のレコードに持って行きたいとします。
 
1,DAT
2,DAT
3,END
4,DAT
5,DAT
 
その場合、下記のように置換すると、上手くソートできます。
(改行コードはCRLFとします)
 
1.改行コードを独自の文字(ファイル中に出てこない任意の文字)に置き換える
置換前:\r\n
置換後:@
 
1,DAT@2,DAT@3,END@4,DAT@5,DAT@
 
2.参照機能により、"3,END@"を一番後ろに移動する
置換前:(.+@)(.+,END@)(.+@)
置換後:${1}${3}${2}
 
1,DAT@2,DAT@4,DAT@5,DAT@3,END@
 
3.2の操作について、"END"のレコードが一番最初にきた場合の考慮でもう一度置換
(2の操作だと"END"のレコードが一番最初にきた場合に置換条件を満たさない)
置換前:(.+,END@)(.+@)
置換後:${2}${1}
 
1,DAT@2,DAT@4,DAT@5,DAT@3,END@
 
4.独自の文字を改行コードに戻す
置換前:@
置換後:\r\n
 
1,DAT
2,DAT
4,DAT
5,DAT
3,END

テレワークにおけるコミュニケーションのコツ

コロナ渦の影響で、テレワークが流行っています。
テレワークではコミュニケーションの取り方が変わるため、チャットでのやりとりに慣れていない技術者がコミュニケーションの取り方に悩むという姿を目にします。
 
先日、相談に乗る機会があったので、それを元にうまくコミュニケーションを取るコツを書いていきたいと思います。
 
1.姿が見えない環境であることを意識する
 テレワークでは相手の姿を見ずにコミュニケーションを取ることが多いです。
 そのため、表情やしぐさを読み取り円滑にやりとりするということができません。
 友好的な態度、意欲的な態度、切迫した状況、等はチャット上で示す必要があります。
 
 友好的な態度は、顔文字や絵文字を使い表情を豊かにすることで示すことができます。
 例えば、Slackでは顔文字や絵文字が登録されています。
 登録されている顔文字や絵文字であれば、使って問題無いと思います。
 雰囲気次第では、ネットスラングを使っても良いかもしれません。
 これらのことは、TwitterのようなSNSで慣れておくと勉強になると思います。
 
 意欲的な態度は、文字で示すしかありません。
 対面のコミュニケーションとは異なり、文字に起こさないと察してくれません。
 仕事が欲しい場合は、手が空いたから仕事が欲しい、と明確に書く必要があります。
 そこで作業が無いと言われた場合は、自分で作業を提案する必要があります。
 (ドキュメントの改善、自分が携わるシステムの勉強、等)
 
 切迫した状況を伝えるには、現在の状況や作業の期限を明確に書く必要があります。
 音声でチャットしたい、と書くだけでも暗に急ぎであることを示すことができます。
 音声チャットは、相手の時間を取らせる代わりに、早く要件を終わらせられます。
 
2.どれが自分の書き込みなのかを認識しやすいようにする
 リアルのコミュニケーションでは顔や声で個人を認識できます。
 しかし、文字だらけのチャットでは、すぐに個人を認識できない場合があります。
 これはコミュニケーションを難しくさせ、時に認識齟齬の原因にもなり得ます。
 
 そのため、チャットツールのアイコンやアバターを設定すると良いです。
 アイコンやアバターにより、視覚的に個人を認識することができるようになります。
 
3.スルーされても気にしない
 リアルのコミュニケーションでは、声をかければ何かしら反応が来ます。
 しかし、テレワークでは、チャットに書き込んでも長時間反応がないことがあります。
 これは不安に思うかもしれませんが、多くの場合は問題ありません。
 システム開発は、作業の性質上、作業に没頭する時間が必要です。
 作業に没頭している時はチャットに気付きにくくなります。
 そのため、長時間反応がなければ、作業に没頭しているだけだと思いましょう。
 
 本当に急ぎの時は、チャットとは別ルートで通話を申し込むと良いです。
 逆に言うと、作業に没頭する時も、別ルートの申し込みはチェックするべきです。