どうでもいいプログラム研究所

とある編集者によるIT、Web、ソフトウェア、プログラミングに関する雑記と覚え書き

「APIとは何か?」を初心者向けに思いっきり丁寧に説明してみた

先日、XでフォローしているVBAerであるちゅんさんがこんな記事を書いていました。

www.excel-chunchun.com

APIはいろいろ種類がありますが、この記事は主にWindows APIを利用するVBA利用者を前提に書かれたものです。VBAでは「Windows API」を用いて、VBAが標準で提供する機能以外にWindows OSが提供する機能を利用できます。

このAPIという言葉は、ソフトウェア開発者であればイメージはつくのですが、そうでない方には非常にイメージしにくいものです。

そこで今回は、「VBAを少し触っている」「プログラミングを初めたばかり」「自分でプログラミングしないがちょっとだけITに関わっている人」くらいのITリテラシーの人に向けてこのAPIのイメージをもう少し噛み砕いて説明しようと思います。

一般人向けに説明する「API」の意味

まず、VBA利用者ではなく、一般人向けに厳密でないざっとした定義を書くと、APIとは

「あるシステムやアプリの機能や情報を他のシステムやアプリが利用したり参照できるようにすること」

です。

例えばタクシー配車アプリでも何でも良いですが、アプリにはよくGoogleマップが埋め込まれています。なぜGoogleのサービスでもない配車アプリが他社のサービスであるGoogleマップを使えるのか。

これはGoogleマップがその機能を他でも使えるように「API」というものを提供していて、それを別のアプリが利用しているからです。Googleが勝手に使っていいよ、としているわけです(もちろん、API使用料を払う、認証するなどもろもろのルールに則っている前提で)

APIの説明としては以上です。一般人にAPIとは何かを聞かれたらこのくらいで良いと思います。

ただ、今回は少しその先の話をします。

上にもあるように、「APIが提供されている」「APIを利用する」という表現が使われています。そこでプログラミングを少し経験した昔の私は疑問に思ったわけです。それって具体的に何をしているのか、と。

アプリを利用するといったとき、そこには当たり前ですが「アプリ」という実体があります。それはプログラミングのコードによってでき上がったものです。

ただ、「APIを利用する」といったとき、それはプログラミングにおいて具体的に何を指しているのか。

一般人はそんなことを気にしないかもしれません。しかし、まだ当時APIを触ったことのなかった昔の私みたいに、多少プログラミングをかじった人は少し気になる人がいるかもしれません。

以下、それを見ていきます。

APIとは「仕組みや仕様」を指す概念である

APIとは具体的に何なのかという答えに近づくために、上に書いたAPIの定義をもう少しだけ厳密に書いてみます。よく一般的に言われている辞書的な定義を私なりに噛み砕くと、APIとは

あるソフトウェア(プログラム)の機能やデータを、外部のプログラムから呼び出して利用するための『仕様や仕組み』

です。最後の部分『仕様や仕組み』これが大事です。

先に結論を言うと、APIとは実体として何かを指す言葉というよりも、そのルールや決め事を指す「概念」です。

しかし、この定義を読んだとしても、まだ多くの人はぼんやりしているはずです。言葉では何となくはわかってもプログラミングをしたことない人には明確にイメージできないと思います。

というのも、私も自分がAPIを利用してプログラミングを行うまでイメージができていなかったからです。

なので次から例え話を交えて説明します。

API連携を「実世界のもの」例えたらどうなるか

ここで例え話を出します。APIとはプログラムが別のプログラムを使うということではありますが、わかりやすいように実世界の物理的な連携に例えてみましょう。

ある公園サービスを運営しているAさんがいます。ここでは、広場、遊具、ベンチなどいろんなものがあります。(ちょっとリッチな公園でいろんな設備や機材もあるとします)

次にイベントサービスを運営しているBさんがいるとします。このBさんは、Aさんの公園という場所を使って「お祭り」を企画しました。

当然、この公園はAさんのものであり、Bさんのものではありません。この公園でお祭りイベントをやるには、Aさんに公園の物や設備を借りるなど、BさんはAさんと連携する必要があります。(このように、2社が連携するのは物理的なサービスでもアプリでも同じことですね)

例えば、Bさんはお祭りを運営するにあたって公園の管理物である「水道」を使いたい、「テーブル」を使いたい、とします。

さて、このときどうすれば事がうまく回るでしょうか。

とてもシンプルに考えればBさんはAさんと会話し、「テーブルはどんな種類があるの?」「水はどれだけ使っていいの?」「何時まで使えるの」とか打ち合わせした上で利用許可をもらい、お祭りを当日運営していきます。

ではここで、Bさんだけでなく、別のイベント事業者Cさん、行事で使う学校関係者Dさんも公園のサービスを利用したい場合どうでしょうか。Aさんはまたゼロから交渉したりするのは非常に面倒です。

ここでAさんはこう考えます。

もともとは自分の公園サービスのために用意しているインフラや設備、備品だけど、
・「第三者の事業者が使えるように」
・「ルールや値段など条件をあらかじめ決めておいて」
・「申請方法とか利用の仕方を決めておいて」
・「あとは勝手に利用してもらえるように」
しておけば、いちいちAさん、Bさん、Cさん…とか今後利用したい人と個別に交渉しなくて済むのではと。そこで、Aさんはこんな書類を用意しました。

公園設備利用申請書です。ここに必要なものを記入して特定の場所(公園管理事務所の受付)に提出してもらい、あと必要な物を受け取るなり使えるようにしておきます。

ただ、何時間、どれだけの量を使えるのか、どんな種類があるのかをあらかじめ利用者に分かるようにしなければいけません。そこでAさんは利用申請書のほかに、利用ガイドも用意しています。

以上です。これがAPIを例えたものなのですが、この例においてAPIは何を指しているかというと、この仕組み全体を指しています。

念のために書くと、申請書がAPIでもありませんし、利用ガイドがAPIなのではありません。APIとは、「仕様」などを含めてサービスを他の人に利用してもらい両者が連携するための仕組み全体を指す言葉なのです。

この「仕様」というのがポイントです。

例えば上記のように申請書をつくったとしますが、利用者Cさんが公園側で用意してないのに「ガスを借りたい」と書いても、利用できません。連携のためには「何をどんな条件で利用できるかというあらかじめの決め事」が必要です。

ここで、プログラムの話に戻ります。

例えば、あなたがもし何か新しいアプリを開発して、そこでGoogle Mapの機能を使いたいとします。

Google MapはAPIを提供する、つまりGoogle Mapの機能を別のプログラムから呼び出してもらうとき、「こういう情報を呼び出したいときは、こういう手順でプログラミングしてください。そういうふうにコードを書いてくれれば、こういう情報を教えてあげますよ」ということを「決め事」として用意しています。

それを沿ってプログラミングのコードをかけば、だれでもGoogle Mapや地図情報を自分のアプリの中に取り込めるわけです。あなたはわざわざゼロから地図機能をつくる必要はありません。

じゃあ「決め事」とは何かというと以下のコードを見てください。

--------------------------------------

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Google Maps サンプル</title>
  <style>
    #map {
      width: 100%;
      height: 500px;
    }
  </style>
</head>
<body>
  <h1>Google Maps サンプル</h1>
  <div id="map"></div>
  <script>
    function initMap() {
      // マップの初期化
      const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 15,
        center: { lat: 35.6812, lng: 139.7671 },
        mapTypeId: 'roadmap'
      });
      // マーカーを追加
      const marker = new google.maps.Marker({
        position:  { lat: 35.6812, lng: 139.7671 },
        map: map,
        title: '東京駅',
        animation: google.maps.Animation.DROP
      });
    }
  </script>

  <!-- APIキーを自分のものに置き換えてください -->
  <script async defer
    src="https://maps.googleapis.com/maps/api/js?key=[APIキーが入る]&callback=initMap">
  </script>
</body>
</html>

--------------------------------------

これは生成AIで出力したものを私が少しいじったものですが、HTML上にGoogle Mapで東京駅を中心に表示させるコードです。Google Map APIを使っています。Google Cloudでプロジェクトを作ってAPIキーを発行して、上記の[ここにAPIキーをいれる]にその値を入れてこのコードをコピペすればすぐに動きます。

ここでいう決め事とは、例えば

GoogleAPIサーバーにアクセスする

(こちらの部分:<script async defer src="https://maps.googleapis.com/maps/api/js?key=[ここにAPIキーをいれる]&callback=initMap">)

とか、

・マップを呼び出して描画する場合はnew google.maps.Map・・・・みたいに書くとか

・対象の地点は緯度と経度で書く(lat: 35.6812, lng: 139.7671)

とかそういうもろもろです。

例えば上に、”center:”という文字があり、これは描画する地図の中央がどこかを書く場所なのですが、”centre:”とか書いたらそれだけエラーになります。全部書き方が決まっているわけです。

APIと関数の関係

さて、ここからはもう少し突っ込んだ話です。

ちゅんさんのブログ記事には、

APIApplication Programming Interface)とは、あるアプリケーションの情報を、プログラミングによって外部から取得したり、変更したりするための接続点のことですが、実際には特定の関数を指して使われることが多いです。

とあります。

これはあくまでVBA向けの文脈で語っているため、このように表現されています。

プログラミングの世界では、APIの仕組みを用いてなんらかの機能を利用する際は、「関数」を使うことが多く見られます。VBAがその例です。だから「API=関数」というイメージなのです。

しかし、厳密に言えば、APIは先述のように原義としては仕組みや仕様です。しかもAPIは必ずしも関数を用いません。詳しくは説明しませんが、必ずしも関数を用いないAPIの例としてWeb APIなどがあります。

Web APIの例に、zipcloudという郵便番号に関するAPIサービスがあります。これはURLでサーバーにアクセスしてほしい情報を得る(より厳密に言えばhttpリクエストによって情報をやり取りする)のですが、例えば、以下をブラウザのアドレスバーに入れてアクセスしてみてください。

https://zipcloud.ibsnet.co.jp/api/search?zipcode=1000001

結果には謎の形(JSONという形式です)でいろいろ書いてありますが、東京都千代田区という情報が返ってきていることがわかります。

APIは文字通り、ユーザーインターフェースでなくプログラミングインターフェースなので、このように人が手動でAPIを打つシーンはありえないのですが、「URLにアクセスして情報が取得できる」という意味がわかったと思います。

APIは、どう分類するかにもよりますが、整理するとこういう分け方が可能です。

API(呼び出し方法で分類)
├── HTTP経由で呼ぶ(Web API
│   └── REST、SOAPなど

└── コード内で関数として呼ぶ
    ├── OSの APIWindows APIなど)←ちゅんさんが話しているのはここ
    ├── ハードウェアのAPI

 

APIとライブラリの関係

さらに、ちゅんさんの記事には、以下のようにAPIは関数なのかライブラリなのか。みたいな観点での解説がありました。

関数群をAPIと呼ぶならば、その関数群を内包しているライブラリもAPIと呼んでいいのではないか?と思ったかもしれません。

確かにVBAを利用する人にはそのようなイメージがわくかもしれません。これはちゅんさんが書いているように「同じと言えなくもないが、別物と捉える」が無難な回答です。

ライブラリは内部でAPIを呼ぶことがあるため、文脈や対象によっては間違いではありませんが、一般的にはAPIとは呼ばれません。

「別物と捉える」の根拠

別物と捉える理由は、扱ってみるといろいろと違うことがあるからです。どのように違うかは、これはちゅんさんの記事に書いてありますので読んでみてください。私はもう1つ別の観点から書きます。

使用しているライブラリがその裏側の処理で内部的にWindows APIを利用しているものは、API=ライブラリといえなくもないが、

必ずしもすべてのVBAのライブラリがWindowsAPIを利用していないから」というのが、API=ライブラリとは言えない1つの回答です。

例えばVBAのライブラリには正規表現を利用できるライブラリがあるそうですが、あれは(調べたところ)特にWindows OSの機能を利用しているわけではなく、C++で書かれた文字列処理プログラムを呼び出しているだけです。

「同じと言えなくもないが」の根拠

これは先ほど言及した、「使用しているライブラリがその裏側の処理で内部的にWindows APIを利用している」からという理由に加えて、もう1つ別の理由があります。

例えば、上に挙げた正規表現を利用できるライブラリは、C++のプログラムをVBAで関数を呼び出し、実行結果がユーザーの操作しているVBAのプログラムに戻ってきているものです。

あれ、この話はどこかでみたような…

ここでもう一度APIの定義に振り返ってみてください。

あるソフトウェアの機能やデータを、外部のプログラムから呼び出して利用するための『仕様や仕組み』

あれ、VBAというプログラムがC++のプログラムを呼び出しているということは・・・

つまり、これも非常に広義に見ればAPIであると言えないでしょうか?

これはどう捉えるかの問題であり、賛否両論あると思います。(※ちなみに生成AIに聞くと、PythonやらJavaやらプログラミング言語が持っている標準ライブラリもAPIの一種と言えるとは言っています。

ただ、それでも別物と扱う理由は、APIが仕様や仕組みである、という原義はもちろんですが、「利便性」という理由もあるでしょう。

ちゅんさんの記事にもあるように、裏側の複雑な処理が隠蔽されているかいないかという点でライブラリの関数とWindowsAPIの関数はかなり違うので、「どっちも本質的には同じ」と広義で捉えるのでなく、「それは違うもの」ととらえて区別するほうがはるかに実用的です。

最後に

以上、APIとは何かというのを長々と書いてきましたが、なんとなくわかってもらえたでしょうか。

私はIT業界の編集者として長いだけで、自分自身がソフトウェア開発の仕事をしているわけではないので、もし間違っているところがあればぜひご指摘ください。

Google CloudのIdentity-Aware Proxyを使ってApp Engine上のサイトにアクセス制御をかける方法

最近、Google Cloudが古くから提供しているPaaSのGoogle App Engine(GAE)で社内用のアプリケーションを作りました。基本的なセキュリティ対策として、自社のGoogle Workspaceアカウントと関連会社のGoogle WorkspaceアカウントだけがアクセスできるようにしたくてIAP(Identity-Aware Proxy)を設定しようとしたのですが、なぜかうまくいかず、いろいろ試行錯誤したうえでようやく解決しました。忘れないように、今回は備忘録がてら解説を書こうと思います。

 

IAPとは何か

はじめにIAPというものを整理しておきます。IAPとはひとことでいえば、Google Cloud上に構築したWebページやWebアプリケーションへアクセスする際、ユーザーのIDに基づいてアクセスの可否を制御できる仕組みです。今回はGAEですが、Cloud Run、Compute Engine、Google Kubernetes Engine (GKE)などで構築したページにも適用可能です。

IAPというのはGoogle Cloudでの呼び方ですが、他の主要パブリッククラウドでも同様の機能が提供されています。

IdentityというのはユーザーのID。Awareは「認識して」という意味。Proxy(プロキシ)は、ユーザーがWebサイトへアクセスする際に、ユーザーに代わってインターネット通信を中継するものです。

イメージとしては、Google Cloud上のWebページとユーザーの間に代理人がいて、その代理人がアクセスしたユーザーのIDを確認し、それが許可されているものであった場合は代理人がユーザーの代わりにWebページにアクセスして、その結果をユーザーに返してあげる、という感じでしょうか。

詳細は、Google Cloudの公式ドキュメントをご覧ください。

 

IAPの設定方法

以下、IAPの設定手順を紹介します。

まず、Google Cloudの左側のメニューの「セキュリティ」>「Identity-Aware Proxy」から設定します。ただし、この機能を利用するためにはAPIを有効化する必要があります。「APIとサービス」の中からでも設定できますが、最初に「Identity-Aware Proxy」へアクセスすると、APIを有効化するボタンがあるのでそこからでも大丈夫です(下図)。

IAP画面での設定はシンプルです。以下の私の環境の画面では、すでにGAEでアプリを構築しているので、「すべてのウェブサービス」というところにGAEアプリがあります。

対象のアプリを選択して、IAPのトグルボタンをオンにした上で、下図に示す右側のサイドパネルの「ロール/プリンシパル」で権限を設定します。

ロールというのはそのユーザーに何を許可するかという項目です。私もそれぞれのロールの詳細は把握していませんが、今回行いたいのは、ユーザーがブラウザから対象のWebページへのアクセスできるようにする設定です。これを実現するためのロールは、下図にある通り「IAP-secured Web App User」というロールであり、そこにアクセスを許可するユーザーのメールアドレスもしくはドメイン名を設定していきます。

ちなみに、今回紹介するこのIAPはGoogleの認証情報をアクセス制御の判断材料として使うので、ユーザーやドメインGoogle アカウントと紐づいていることを前提とします。このIAPでアクセス制御できるのは、Googleアカウントだけです。それ以外のアカウントの場合、GoogleとID連携を行うことで可能になるそうです。

これだけではまだIAPは機能しません。次に「OAuthクライアント」の設定を行います。

この「OAuth」とは何なのか?について説明すると長くなるので省きますが、認可のためのプロトコルの1つです。このプロトコルでは、「OAuthクライアント」「シークレット」「アクセストークン」のキーワードがよく出てくるので、先にその言葉を説明します。

OAuthクライアントとは、例えるならアクセスしようとしているユーザーが「入場するための招待状」みたいなものです。IAPが「誰か」を識別してアクセス制御するためには、当然ながらアクセスしようとしている人が匿名であっては識別できないからです。

ただ、それだけでは十分ではありません。例えば、Aさんの招待状が盗まれ勝手に使用されたら、Aさんの招待状でBさんもCさんも入れてしまうからです。なので、この招待状をAさんが使用するときに、それが「本当にAさんのものである」ことを証明するものが必要です。それが「シークレット」です。

こうして、招待状を持って通行しようとした際に、守衛の人は認可サーバーに確認を取り、確認が取れたら入場券をユーザーに渡します。この入場券がアクセストークンです。

 

OAuthの設定

このOAuthの設定を行うには、IAPの設定画面で、対象となるアプリの右の方にある3点メニューから「設定」をクリックします。

すると「OAuthの構成」というところに、

  • Google 管理の OAuth(シンプルに行うためにはこちらを推奨)
  • カスタム OAuth(特定の制御、ブランディング、外部ユーザー用)

という2つの選択肢があります。

このうち、今回は外部のユーザーのアクセス制御を行いたいので、「カスタム OAuth」の方を使用します。上記の画面に書かれているように、これを構成するには、まず「OAuth 同意画面」を構成する必要があります。そのため、「カスタム OAuth」のラジオボタンを選択する前に、その下にある「同意画面を構成」をクリックしてその設定を行います。

ちなみに、「OAuth 同意画面」というのは、その名の通り、「OAuthという仕組みに基づいて、あなたのGoogleの認証情報を使ってアプリを通信できるようにしますよ。許可しますか?」というユーザーの合意を取るための画面です(その画面は実際このあとの説明で示します)。

この「同意画面の構成」はGoogle Cloudの全体メニューの「Google Auth Platform」>「OAuth同意画面」から行います。ここは最初にアクセスすると、まだ設定がなされておらず、以下のように「Google Auth Platform はまだ構成されていません」と表示されます。

上記画面で「開始」を押したあと、プロジェクトの構成で、

  • アプリ情報
  • 対象
  • 連絡先情報

の3つの情報を設定します。

対象は、「内部」と「外部」ありますが、今回は組織以外の人にも共有するため「外部」を選択します。(※以下の画面はすでに「外部」を設定済みの画面です)

これで最後に「作成」を押すと、OAuth 同意画面の構成はいったん完了です。ただ、これでまだ完了ではありません。先ほども触れましたが、アクセス制御するには、認証情報が必要です。具体的には、身分証と本人を証明する「OAuthクライアント」と「シークレット」が必要です。

これは同じく「Google Auth Platform」のメニューから「クライアント」を選択して、手動で作成できるのですが、それより簡単な方法があります。先ほどのOAuthの構成を設定する画面(下図)に、「認証情報を自動生成」というボタンがあるのでそれをクリックすると自動で発行されます。

これを実行すると、上記の画面の「クライアントID」と「クライアント シークレット」という部分に自動的に入力されます。

実際に「Google Auth Platform」のメニューから「OAuthクライアント」を見てみると、以下のように、OAuthクライアントが自動的に生成されていることがわかります。

このOAuthクライアントに関して、右側の鉛筆アイコンをクリックして設定を見てみると、その中に「承認済みのリダイレクト URI」という設定項目があるのですが、それも自動的に入力されています。このURIは以下のような形になっており、赤い部分にOAuthクライアントIDが入るかたちとなっています。

https://iap.googleapis.com/v1/oauth/clientIds/xxx.apps.googleusercontent.com:handleRedirect

「承認済みのリダイレクト URI」とは認証コードを安全に受け渡すために存在します。ユーザーがIAPを通じて対象のサイトにアクセスする際、まずGoogleの認証情報でログインする許可をGoogleの認証サーバーでもらったあとに、今度はアクセスしようとする対象のサイトに認証コードを渡さなければなりません。

ここで、もし認証サーバーで認証した後に、任意のページにリダイレクトできてしまった場合、例えばサイバー攻撃者が何らかの形で介在できる余地を残してしまい、認証サーバーから返ってきた認証コードが窃取されてしまいます。そのため、「承認済みのリダイレクト URI」という項目で、リダイレクト先はあらかじめ特定のものに設定しておく必要があるのです。

 

実際にサイトへアクセスしてみる

さて、これにてIAPの設定とOAuthの構成が完了したので、実際に対象のアクセス制御対象のWebページのURLにアクセスしてみましょう。

以下のように、Googleのサービスにログインする際に現れるアカウント選択の画面が出てきます。

事前にアクセス許可リストに入れたアドレス(Google Workspaceに紐づいたドメイン)を選択します。次はこのような画面になります。

実際のアクセス先のサイトはテストページで何の変哲もないので割愛しますが、上記画面で「次へ」を許可すると無事にサイトにアクセスできました。

今回は事前に許可されたアドレスだったのでログインできましたが、もしアクセス権限が与えられていないGoogleアカウントでアクセスすると、以下のようにアクセスが拒否されます。

ちなみに、今回は組織外部のユーザーを含めたアクセス制御を実現するユースケースとして、OAuthの構成で、「外部」を選択しましたが、これが自社の従業員だけがアクセスできるようにしてそれ以外のドメインGoogleアカウントからのアクセスを拒否したい場合、つまり「内部」の場合、設定はもっとシンプルに行うことができます。

その方法としては、先ほどのこちらの画面にて上の「Google 管理の OAuth(シンプルに行うためにはこちらを推奨)」を設定するだけでOKです。

これはGoogleがもともと内部的に用意されているOAuthクライアントを使っているらしく、この場合ユーザーが明示的にOAuthクライアントとシークレット、承認済みURIなどを設定する必要はありません。

 

注意点

1つだけ注意点です。Webブラウザを通じたサイトへのアクセスは、すでに説明した通り、「IAP-secured Web App User」というロールに、アクセスさせたいプリンシパルを設定していますが、ここに管理者は自動的に追加されません

つまり、このアプリのプロジェクトオーナーのアドレスまたはドメインを手動で追加しない限り、アプリのオーナーですらアクセスがブロックされます。

「オーナーなんだから自動的にアクセス権を付与しても良いのでは」と思ったのですが、私と同じ疑問を持つ人が多いのか、公式ドキュメントのIAPのクイックスタートガイドのページに、ちゃんとその理由が書かれていました。

つまり、「人事システム」というシステムの開発と運用を管理するのは情シスだが、実際に人事システムにアプリにアクセスするのは情シスではないユーザーなのだから、情シスには自動的にアプリへのアクセス権は付与しないよ、ということです。まぁ確かにそう聞くとその通りですよね。

以上です。ちなみに、これは私が社内アプリのアクセス制御のためにいろいろGeminiに聞きながらたどり着いたもので、IAPに関して詳しいものではなく、むしろ普段システム開発に関わっていない人間であり、素人です。

素人がこの設定を実現するのに非常に苦労してしまったので、備忘録的に残しておきました。上記に書いてある手順が正しいかどうか、他にも設定するべきところがあるか不明ですので、あくまで参考程度にご覧ください。

 

Twitterでバズる条件はよくわからない【雑記】

今日は雑記です。以前Excel界隈のツイッターアカウントで超大バズしたツイートがあり、それに対するちょっとした考察です。

Excelネタで23万いいねの大快挙

その大バズした話題のツイートがこちら。

この方は本職は医師の方で、Excel関連の情報をつぶやくTwitterアカウントをやっています。私はフォローしていませんが、よくタイムラインに流れてきて、時折役立つTips系のツイートをしています。もともとフォロワーも多く、バズったのはこれだけでなく、以前も結構なバズツイートを度々出していました。

ご本人のツイートによると、今回のこの23万いいねのツイートによって、フォロワーが一気に5万人近く増えたとのこと。めったに見られないまさにミラクルレベルの増加数です。

これに対して今回私が記事を書こうと思ったのは、以前以下のとあるツイートを見てから。

私がフォローしているreimeさんがつぶやいたもので、このツイートを見たとき私も、「あーやっぱり私以外にも同じこと思っている人がいたか」と感じました。

あのExcel医さんのツイートが大バズしたとき、確かに知らない人にとっては役立つ内容だし、バズってもおかしくはないとは感じましたが、私の率直な印象は「そんなにバズる内容?」というものでした。

「これくらいのショートカットは常識だろ」という意味ではありませんし、Excel医さんに対する僻みでもありません。わかりやすい良いツイートだと思います。あくまで純粋な疑問でした。 内容的に1~2万いいね、とかならわかりますが、「23万いいね(当時は10数万いいね)行くほどか?」と思ったわけです。

一応私もExcel界隈の端くれではあるので一般人より多少はExcelの知識はあります。大半の人が知らないものに対して「それくらい誰でも知ってるだろう」と思ってしまうバイアスはあるかもしれませんが、そのバイアスを差し引いたとしてもここまでバズったのはとても不思議でした。

というのも、Excelやらパワポやらいろんなツイートが日々流れてきて、似たようにショートカットのTipsツイートはTwitterだと多いです。実際にそうしたツイートも数百いいね、数千いいねとかついているわけで、確かにExcelネタはウケやすいのは事実なのですが、今回のツイートがそれらに比べてそんなに差異があるようには感じなかったというのが私の感想です。

ではなぜバズったのか?

私はSNSの専門家ではないので詳しいことはわかりません。なぜバズったのかについて後付けで考えてみると、

  • 「覚えるなよ。ぜったい覚えるなよ」という煽りからの有益情報のギャップがよかった
  • ショートカット系Tipsはテキストでツイートする人が多い中、デザインがよく見やすくシンプルな画像で見せた

みたいな小さな要因はあると思います。あとはもともとExcel医さんはフォロワーが1万人近くおり、拡散力が高いという要因もあります。私の推測ですが、これら以外の大きな理由の1つとして「Twitterアカウントの見た目のイメージ」があったのではないかと思っています。

Excel医」というアカウントでアイコンもExcelExcel医さんは、私は以前から「Excelを勉強している医師の方である」、という意味は知っていましたが、もしかしたらタイムラインで初めてみた人は「Excel医っていう名前からすると、Excelの専門医(専門家)なのか?Excelの詳しいこと教えてくれるアカウントなのか」と解釈する人も確実にいただろうなと思います。

まったくExcel関係ないアカウントで同じ内容をつぶやいても、そんなに拡散もしなかったでしょうし、フォロワーも増えないでしょう。

フォロワーが一瞬にして増大したのは、Excelに特化しているアカウントであることを前提としつつ、Excel×医師というめったにない組み合わせ、かつ医師という権威性の高いアカウント、というのが大きな要因だったのではと私は推測しています。

あとは「ショートカット」という一般人にわかりやすい内容だったことも大きいでしょう。

Excel界隈のTwitterアカウントを見てみると、そういうアカウントはExcelに詳しいため、実は基本ショートカットみたいな内容をつぶやいている人はほぼ皆無だったりします。Excelショートカットをつぶやいているのは、Excelアカウントではなくて大半がビジネスマン向けのアカウントです。

そんななか、「フォロワー数多数のExcel系アカウント」が「超基本Excelショートカットをつぶやく」という、灯台下暗しというか、ありそうでなかったツイートだったことも大きかったのかもしれません。

何がバズるかわからない

みな口を揃えていいますが、バズると思わなかったものがバズるのがSNSです。ほぼ同じ内容のツイートでも片や全然バズらず片や盛大にバズっている例もよく目にします。

今回の件を見ると、改めてSNSって奥深いものだなとつくづく思います。

 

情シスから嫌われないエンドユーザーコンピューティングのために

f:id:tdyu5021:20210207231150p:plain

情シスではなくエンドユーザー自身でプログラムを作って業務を効率化することをエンドユーザーコンピューティング(EUC)といいます。エンドユーザーにITのスキルがあれば会社にとって大きなメリットですが、本当に問題点はないのでしょうか。エンドユーザーと情シスの関係性で私が感じたことを書きます。

エンドユーザーコンピューティングの再来か

一般的に会社は情シス部門みたいなところがITツールの運用だったりプログラム開発を行っていますが、EUCという言葉があるように業務部門の1ユーザーが自分でプログラムを開発したりして業務を効率化しているケースもあります。

ちなみに私は5年前から現職(IT業界専門の制作会社)に転職し、IT業界を取材し続けていますが、実際のところ最近はこの言葉はあまり使われていないようです。この用語で検索しても過去の記事がたくさんヒットしますし、取材の中で「EUC」という言葉を言及する人は割と上の世代が多い印象です。

EUCの定義は私も厳密には把握しておらず間違っているかもしれませんが、最近は改めてEUCの流れが来ているように思います。

というのも、最近はエンドユーザーがITを取り入れるハードルは明らかに下がっています。Twitterを見ていると、直近では「ノーコード/ローコード」も頻繁にタイムラインに流れてくるので、これからそうしたツールはますます流行ってくるでしょう。DXの流れもあり、ノンプログラマーシステム開発に携わるチャンスは増えてくるかもしれません。

IT製品導入だって情シスのお世話にならずとも、いまや勝手にクラウドサービスを契約して利用することだってできます(シャドーITの別の問題が出てきますが)

あと、これは目新しいものではありませんが、どの人のパソコンにも入っているExcelでもVBAを使ってれっきとしたプログラミングができます。持っているユーザーは限られそうですが、Accessなどを使っても簡易的なシステムが作れます。

エンドユーザーコンピューティングにデメリットはないのか

Excel VBAによって業務を効率化した話はちまたに腐るほどありますし、そのメリットはいまさら語る必要もないでしょう。エンドユーザーがプログラムをわかっているに越したことはありません。ただ今日の記事の本題はその先です。

エンドユーザーがプログラムを作れることが当たり前の時代になったとき、どのような問題がでてくるかということです。

問題の1つは「シャドーIT」のリスクです。シャドーITとは広義には情報シスの管轄になくユーザー自身が業務で勝手に利用しているITのことです。わかりやすい例として「会社で正式認められていないが勝手にSlackを導入してチーム内のコミュニケーションに使っている」というのがあります。自分で密かにプログラム開発しているというのもその例です。

Slack程度なら会社内で許可をもらうことはそこまで難しくないかもしれません。もっとたちの悪い狭義のシャドーITは、商用利用前提でないものを業務で使うことです。例えば「私用のスマホの個人LINEで業務連絡する」というイメージです。

シャドーITを問題だとに認識するのはほぼ100%情シス部門です。一方で現場で勝手にシステムを作ったり使ったりする当のエンドユーザー本人はほぼこの問題に関心はないでしょう。

それも無理はありません。会社全体のITを管理下に置きたいのが情報システム部門の思いですが、エンドユーザーからしてみたら「自分の周辺の業務が効率化できればいい」というくらいにしか思っていないからです。

ですが、そうした感覚は非常に危険であり、今すぐにでも是正されなければならないと私は考えています。その理由は次の項で書きます。

とばっちりを受ける情シス

そもそも、エンドユーザーが勝手にシステムを導入したりプログラムを開発した場合、「その面倒を誰が見るのか」という話がつきまといます。いわゆる運用の問題です。もしEUCによって導入されたプログラムが社内に広まったとき、開発者はエンドユーザーであるにもかかわらず、付随する問題が生じて情シスに質問が寄せられるケースは往々にして存在します。

情シスからしてみたら「なんで自分がこの問い合わせ対応しないといけないんだよ」と思うわけです。ほかの人が勝手に始めたIT活用で自分の仕事が増えたらたまったものではありません。

もう1つがトラブルの対応です。情シスが絡んでいないシステムやプログラムに何か問題があり、それによって業務に影響が出たとします。Excel VBA程度ではあまりない思いますが、もしセキュリティの事故が発生したら、会社への影響は大きなものです。

ですが、そのときに情シスとしては「このプログラムはエンドユーザーが勝手に入れたものだから私たちは知らない」とは簡単にいかないわけです。情状酌量くらいにはなるかもしれませんが、情報システムに関する尻拭いは残念ながら情シスなのです。

つまるところ、EUCによってプログラムが作られたとき、それに対する責任が取れなければ社内に逆にいろんなところに迷惑になってしまう可能性があるというのが問題点なのです。

こうしたリスクを指摘したところで、実際には当のエンドユーザー本人の心にはそこまで響かないでしょう。会社全体や情シスに起こりうるリスクへの良心の呵責が大きければ響くかもしれませんが、現実的にそれは、リスクを気にせず勝手なシャドーITを入れて業務効率化するメリットを上回ることはないでしょう。

「会社の全体最適を目指すべき」とか「情報システム部門に迷惑をかけてはいけない」などの正論ではなく、自分の仕事やその回りの業務が楽になるかという自分へのメリットがあるかどうかの判断で人は動いてしまいます。

ここで念のため付け加えますが、もちろん情シスの統制の範囲内にあり、セキュリティリスクのない簡易かつ自分自身の業務を効率化する用途のVBAのプログラム程度であれば問題ありません。ここで私が想定しているのは、情シスが管理していなく、なおかつ社内のいろんな人がそのプログラムを使って、それがないと業務が回らないようなものを想定しています。

エンドユーザーが知っておくべきこと

ただ、エンドユーザーとしては短期的には業務効率化ができたとしても、長い目で見て自分に跳ね返ってくるデメリットもあります。とっつきやすいエンドユーザーのプログラミングとしてExcel VBAを例に考えてみましょう。

いくら自分が業務を効率化してメリットがあったからといって、それが間接的に情シスの迷惑となったらどうでしょうか。そしてそれが世の中のいろんな企業で起こっていたとしたらどうでしょうか。

結局「Excel VBA」はいつまで経っても「中途半端なシステム」として情シスやプログラマー界隈からないがしろにされるだけです。Excel VBAは正しく扱い、特定の用途には効果を発揮します。しかし、その領域をわきまえずそれで何でもそれで構築しようとすると、エンタープライズのシステムではどこかで限界が生じます。

それはいろんな企業のシステム導入事例で「脱Excel」というスローガンが叫ばれていることからもよくわかります。(※注:脱Excelは、VBAピンポイントの話ではなくワークシートでの情報管理から脱却する、という話が多いですが)

Excel VBAが嫌われてしまう理由

情シスだったりプログラマーの人からの意見として、Excel VBAは低い評価を受けることがあります。それはExcel VBAプログラミング言語として貧弱だからという単純な理由だけではないと私は考えています。

ネガティブな印象が出る理由として私が推測するに、おそらくExcel VBAとは、「ITがわからないところに1人だけVBAがよくできる人がいて、その人がどんどん1人で開発していった」というシーンで使われやすいことに関係があると感じています。

これはどういうことか、次のような流れを考えてみましょう。

まずVBA愛用者(VBAer)は、ITがわからない従業員ばかりの職場の中でバリバリ開発してヒーローのような存在になったとします。しかし、それは本格的なシステム開発をしていたり、全社最適なシステムを考える人達にとって中途半端な部分最適なシステムに見えてしまう可能性があります。

そのVBAerもせいぜい「セミプロ」のような存在に映ってしまうかもしれません(もちろんスキルによります)。

ですが、当のVBAer本人はIT音痴な職場を救った達成感や使命感に誇りを持つわけです。そしてそういう人は「情シスに頼らなくても、この部署のIT化は俺がなんとかする!」と思うに違いありません。

こうした「詳しい自分1人でなんとかする」タイプのエンドユーザーが、全体最適のITシステムと相容れないことは想像に難くないでしょう。こうして生まれたプログラムは部分最適を突っ走る形になり、それによってExcel VBAも煙たがられるのだと私は思います。

よくExcel VBAは「属人化しやすいからダメ」という批判があります。それに対する反論として「属人化するリスクはほかの言語でも変わらない」という意見があります。この反論は確かにもっともだと思うのに、なぜExcel VBAだけが属人化批判のやり玉に挙げられてしまうのでしょうか。

それは言語自体の問題ではなく、まさに上に書いた「俺1人でなんとかする」タイプの人(特にエンドユーザー)によってExcel VBAが愛されるせいで、不運なことにもExcel VBAにそういう印象ができてしまった偏見と先入観の問題、というのが私の見解です。

エンドユーザーはどんなマインドを持つべきか

ですから、プログラムができるエンドユーザーにとって、情シスのことを考えなくても自分の業務効率化には関係ないから気にしなくてよい、と考えるのは長い目で見て得策ではありません。

つまり、業務効率化をしようという意識は、辺にプロ意識を持つことで全体最適を考える情報システム部門からは煙たがられてしまい、皮肉なことにもVBA使いであったとしたら、自分の誇りにしているVBAの評価すら下がってしまいかねないからです。

VBAに限らず自身が使用して頑張っているものに対する世の中からの評価が下がってしまうのは悲しいことではないでしょうか。

ではどうすればよいのか。私は情シス担当者でもありませんし、コンサルでもありませんが、おそらくきっと大事であるだろうと信じていることをまとめてみます。

情シスを理解しようとする

EUCを実践するエンドユーザーだって、情シスだってITを使って社内を効率化しようと考えている点で同じです。つまり同志であるはずです。それぞれ対話もないまま独自の取り組みが進んでしまうから、どんどん溝が深くなってしまうのだろうと思います。

これは世の常ですが、エンタープラズシステムの構築において、エンドユーザーが重要だと考えていることと、情シスが重要だと考えていることは必ずどこかに乖離があります。対話がなければその乖離はどんどん広がります。

このことは先日このブログで書いた以下の記事のTwitterの一連のいざこざで強く感じたことです。この物議を醸し出した元ツイートへの引用リツイートを見ると、エンドユーザーと情シスの間で明確に意見が違うことが見て取れます。

tdyu.hatenablog.jp

だからこそ真剣に話し合える環境は必ずお互いのために役立つはずだろうと考えています。

相手のためを思う

いざ情シスと対話するときに、EUC担当は「業務を効率化したい」という思いから「情シスがなんとかしてくれよ」という意図がにじみ出てしまうかもしれません。これが危惧すべきことです。シャドーIT的なことをやっていたり、もしくはEUCで頑張っているということは、裏を返せば情シスがITを整備してくれていないからそうなっているはずです。

つまり、EUCが業務効率化への思いを吐き出す対話の機会があるとしたら、それは高確率で「情シスへの不満」がにじみ出てしまいます。こうなってしまえば対話の空気感は非常にまずくなってしまいます。

しかし、EUC担当と情シスは同志であるはずです。EUC担当側としては「情シスができないなら自分たちで何かできることはないか。情シスが忙しくてできないならリソースをお互い分け合ってできる効率化の仕組みがないか」という対話の仕方をするべきではないかと私は考えています。

システムと業務のトレードオフを理解する

あと、「情シスがITを整備してくれていないからそうなっているはず」と書きましたが、そこには情シス側の事情があります。情シス側がどうにかしたいと思っていたとしても、社員が多ければエンドユーザー全員が楽になるシステムなどそうそうありません。

経営者や情シスにとって便利なシステムを導入したら現場のシステム操作の負担が重くなったなどしょっちゅうあることです。それは仕方ない、ということを私は言いたいのではなく、エンドユーザーは「情報システムはどこかで必ずトレードオフがあるということを理解するべき」ということです。この前提がわかっていないと全体最適なシステムなど作れません。

また、そもそもの話として、単にエンドユーザーが困っているということを情シスが把握していない可能性があります。この場合は情シスに文句をつける前にまずは要望を伝えることから始めなければなりませんが、とにかくお互いが対話するしかないと思います。

情シスを便利屋と思ってはいけない

エンドユーザー自身として「自分が何ができるか」と考えることは、エンドユーザーと情シスの関係に限ったことではありません。当たり前の話ですが、人に何かをしてもらいたいときに、一方的にこちらの要求をつきつけてもうまくいくはずがありません。本気で何かを変えたいのなら、自分もしっかり汗を流すこと、相手がやりやすいようにこちらからも何かできることを提示するべきではないでしょうか。

しかし残念なことに、ITシステムとはインフラのようなものになり、「動いて当たり前」の世界になってしまいました。これは喜ばしいことですが、それに携わる人にとっては悲しい一面もあります。

インターネットがつながって当たり前、PCのトラブルは直ぐに解決できて当たり前……残酷なことに情シスに対しても「社員を助けてくれて当たり前」のように知らぬ間に思ってしまうわけです。

システム導入についても「要件さえいえばあとは情シスが勝手に整備してくれる」と思っている人すらいるでしょう。もっと最悪のケースは「要件もシステム作る人が考えてよ」という思考です。

しかし、そんな態度をする人に優しくできる人間などこの世にいないでしょう。ですから、情シスを「便利屋」のようにこき使うのではなく、労いを持って接するべきではないかと思っています。

まとめ

だらだら長くなり、最後EUCとも少し離れましたが、ここで言いたかったのは以下の4点です。

  1. EUCによる独自プログラムは、業務の属人化や「シャドーIT化」が発生しリスクとなる可能性がある。
  2. 独自にプログラムを組めるEUC担当は、使命感と自尊心が変な方向にいくと、IT部門と相容れない存在となる可能性がある。それはExcel VBAでも起きやすい。
  3. だからこそEUC担当と情シスは対話と連携を行うべき。
  4. その際にエンドユーザー側としては情シスに「助けてもらう」という姿勢ではなく、双方にメリットのあるITによる業務効率化を実現できるように、エンドユーザー側でできることはないか、という相手を思う視点を持つべき。情シスはエンドユーザーのために都合よく動いてくれる便利屋だと思ってはいけない

私もユーザー部門のIT導入や情シスの声を取材し続けてきた外野の人間でしかないので、的はずれなこともあるかもしれませんが、ひとまず私が感じたことをまとめてみました。

Excel VBAから考えるセキュリティリテラシーのあり方

私はTwitterをやっていて、VBA界隈の方々をたくさんフォローしています。いつもは極めて平和的な界隈なのですが、今日珍しくあるツイートがきっかけで荒れていました。ただ、そのツイートには、VBAVBAユーザーが世間やら会社の中での存在感や立場を守る上で大事な観点があったので、私の考えをまとめてみます。

VBA界隈が荒れたきっかけ

今日VBAが界隈が少し荒れたきっかけのツイートが以下のものです。

前提を知らない方のために説明すると、いまTwitter上では、Excel VBAの大御所的な存在である「エクセルの神髄」さん(以下、神髄さん)が、VBA学習者のためにVBAの実務的な練習問題を出す「VBA100本ノック」を定期的に配信しています。その中のとある問題に「RR@IT・情シス勉強中」さん(以下、RRさん)が、セキュリティに問題があるとして引用リツイートで難癖をつけたのです。

上記のツイートを見ておわかりの通り、少し攻撃的な物言いであったために、神髄さんが反論し、さらにRRさんがそれに反論する形で言い合いになり、VBAer界隈でもRRさんの失礼な態度に批判が集まりました。

私はTwitterの基本スタンスとして、炎上していたり賛否両論が集まっている内容にはツイートも引用リツイートもせず、静観する主義なのですが、これに関しては思わずコメントせずにはいられませんでした。

その理由は、RRさんのツイートが「失礼なクソリプ」という印象だけが残ったまま片付けられることで、VBAの立場が中長期的に悪くなるリスク、ひいては世の中のセキュリティ意識が軽視されるリスクが隠蔽されてしまうと考えたからです。その意図を以下に説明します。

そもそもRRさんはなぜ批判したのか

RRさんのツイートの通りですが、RRさんが批判したのは、100本ノックの当該の問題にセキュリティ的に問題があったからです。ただし、VBA100本ノックは、あくまで「練習としてサンプル」であり、実務でそのまま使うことまで想定されていません。セキュリティを保証する義務もありません。

RRさんはそのことを知っていたかどうかは不明ですが、指摘されたVBA界隈から見れば、「こっちは神髄先生があくまで趣味として、しかも『練習問題』として好意でやってるだけなんだから、外野の人間が情シス目線の実用的なセキュリティの話でツッコミを入れるのはおかしい」と思うわけです。

まさにこれを例えたのが、次のツイートです。 

RRさんはツッコミは批判されるべきものであったのか

これに対して私の意見として、まず「神髄さんの赤の他人であるRRさんが神髄さんにあのような口調で絡むのは失礼であり、その意味であのツイートは間違っていた

ということがいえると考えています。その点であのツイートは批判されてやむを得ないものです。

そして、絡み方が失礼か否かを切り離したもう1つの論点が

VBAの個人的な活動とはいえ練習問題でセキュリティが完全でないもの紹介していることに対して外部から批判をするのが妥当であるかどうか」

という論点です。要するに「意義のある批判」か「ただのクソリプか」どうかということです。

これに対して私は前者だと考えています。

RRさんの批判に妥当性があると私が考えるわけ

なぜRRさんの批判について意義があると私が考えるのかは次の理由があります。

もしVBAの100本ノックでやったことプログラムを実務に応用し、不十分なセキュリティのために例えば社内の情報システム担当者に面倒をかけた、もしくは会社に何らかのセキュリティインシデントが発生した――。このようなことが起きてしまったらどうでしょうか。VBAに影響力のある人が、好意とはいえそのようなリスクがあることを情報として配信することに対して、指摘してはいけないことなのでしょうか。

ExcelVBAなどは、高額な情報システムに投資できない会社にとって非常に有益ですが、属人化しやすく、より高度なシステム化をする際に「悪者」扱いされがちです。

今回もそのようなリスクがないとも言い切れないのです。RRさんのツイートには批判が寄せられましたが、一方で引用RTで賛同を示す方も少なくなく、RRさんと同じく情シスと思わしき方からこのような意見もありました。

こうした批判に対する反論として「VBA100本ノックはあくまで練習であり、それを実務で『そのまま』使おうとする人はいないだろう」と神髄さんは思っているでしょうし、VBA100本ノックをやられている方の多くもそう思うでしょう。

ただし、必ずすべての人がそうだといいきれるでしょうか。あまり知識のない人がたまたま神髄さんのツイートを見て、それをそのまま会社の業務に利用してしまう可能性はないといいきれるでしょうか。

だからこそRRさんの最初のツイートに

「これを実運用で使う人が出ないよう、きちんと警告するのがリテラシーだし。適切なユースケースの提示や警告を欠いたサンプルは、VBAを悪者にするだけじゃなくて?」

と書いてあったように、何らかの「注意書き」をするべきというのには妥当性があると私は同意します。

ですから、仮にRRさんのツイートが

「サンプル問題とはいえ、危険じゃない?
マクロの実行を無効化すれば、すぐ開けるし。xlsxファイルの中身はExcelでなくても読めるから。」

で終わっていたら、紛れもない正真正銘のクソリプですが、ちゃんとその後ろに「きちんと警告するのがリテラシーだし。」と書いてあったことは正論だと私は思っています。

RRさんのツイートに対する反論として「セキュリティに完璧などをいちいち求められない」というのがあります。例えば以下のものがあります。

しかし、これらの意見は論点がずれています。RRさんのツイートは「セキュリティを完璧にしろ」とは言ってません。セキュリティの専門家でさえ、セキュリティを完璧にできるとは思っていませんし、これはIT業界の常識です。同じくRRさんのツイートはあくまで「実運用で使う人が出ないように注意書きを書くべき」といっているのです。

ですから、RRさんもそのことを神髄さんに反論すればよかったのに、以下のツイートみたいに「どうすれば安全にできるか」という本題と関係ない論点で噛み付いたので、話が関係ない方向に進んでややこしくなってしまい、批判をさらに招いた形です。

吉田さんのおっしゃるように「セキュリティを完璧にすることはできない」というのはまったくもってそのとおりです。しかし、だからといってそれを「放置する」理由にはなりません。セキュリティが完璧でないなら、「100本ノックの問題として出すのはやめようか」とか「該当のツイートに、セキュリティ上のことわりを一言ツイートする」という代替案があったはずです。

RRさんがいうように、それをできることが「正しいITリテラシーの使い方」だと私は考えています。

それに対する反論として、「では、趣味として行っている神髄さんがそこまで考える必要があるのか」という意見があります。この論点は後ほど説明するのでここではひとまずおいておきます。

RRさんの批判に妥当性があると考えるもう1つの理由

RRさんのツイートに「悪者」と書いてあったように、もしセキュリティに問題があるVBAが実運用され、それが社内の情報システムの中で問題視されたとき、それを作ったのが情報システム担当者自身だったとしても、VBAそのものは悪くないにしても「VBAはやっぱりだめだ」「VBAなんかでシステムが作れない」というような偏見や印象を招きVBAが悪者」扱いされてしまう可能性も否定できません。

これは悲しいことではないでしょうか。これを防ぐためにもセキュリティに関する意見として、RRさんが一石投じたことに私は意味があると考えています。これが、RRさんのツイートに妥当性があると考えるもう1つの理由です。

RRさんのツイートは一見するとVBAerの活動を批判している「VBAerの敵」ように見えます(実際にRRさんもVBAを疑問視している節はありますが・・)。しかし非常に逆説的なことに、RRさんの意見をクソリプと片付けずに耳を傾けることは、セキュリティ意識を高めて中途半端なVBAが業務利用をされない、つまり情報システムの中でVBAの存在を守るために大事な観点なのです。

VBAerの皆さんとしては、自分たちのやっていることがいちゃもんつけられたような感覚になっているでしょう。しかもTwitterVBA界隈のツイートを見ると「あくまで練習問題なんだから」というのを免罪符を使おうとしているようなニュアンスも感じられます。

しかし、そうした考えだからこそVBAのプログラムは「中途半端」なものになり、回りに回って企業の情報システムから「邪魔者」のような扱いをされてしまうのではないでしょうか。

セキュリティの注意書きが必要だったと考える3つの理由

ここまでこの文章を書いてきた私の意見に対してもう1つ反論があるとしたら次のような意見でしょう。

「実用面で不十分だし、注意書きをする妥当性もわかった。だけど、神髄先生の100本ノックは『ただの趣味の活動』である。そんなところにまでセキュリティの啓蒙の責任を追わせるのか。趣味でやっていることに対して外野からとやかく言われる筋合いはない」

これについてはどうでしょうか。

私としては、趣味の活動だとしても「セキュリティの注意書き」をいれるべきと考えています。その理由は大きく3つあります。

1つに、情報セキュリティの事故は会社の業務や日々の活動に大きく関わることだからです。もちろん神髄さんに「法的な責任」は一切ありません。ただし、ITやプログラミングに関わり、しかも影響力が大きい存在だけに、セキュリティに対する注意書きは合ってしかるべきだと考えています。

先ほどRRさんのツイートを以下のように例えたツイートがあることを紹介しました。

この例えで根本的に欠けてて間違っているのは、素振り用のバットを試合に持っていても困るのはバットを使ったチームだけで自己責任の問題だということです。誰かに害が及ぶわけではありません。だから外野が指摘するのはおかしいのです。

一方で練習サンプルマクロの場合は(可能性が低いにしても)情報セキュリティの観点で情報漏えいを含む重大な事故になる可能性があります。

当然ですが、練習バットを使ってチームが勝てなかったことと情報セキュリティの事故を起こすことの間には社会的責任の重さが違います。

これに対して私も空リプ的な感じでよろしくはないのですが、思わず以下のようなつぶやきをしてしまいました。

ここまで読まれた方ならこのツイートの意図はわかるかと思います。この一連の騒動で多くの人はRRさんのツイートに対して「変なやつが絡んできた」という印象しか抱けていないのです。「セキュリティの不十分なマクロを発信することがどうなりえるか」というところまで想像力が及んでいないのです。

次に、2つめの理由として、VBAのような活動は企業が体系的に取り組むというものより、個人の担当者が会社の中で広げていく「草の根的」な活動で(悪く言えば「シャドーIT」的に)広まっていくからです。

そういうふうに広がるということは、まさにTwitterのような非オフィシャルな界隈による影響力が大きいと私は考えています。大きいからこそ、そのような場でVBAの立場が悪くなるようなことには注意を払うべきだと考えているからです。

そして、最後の理由の3つ目について、これはすごく大局的な話で完全に私の主観的、情緒的な理由なのですが、「ITを人に伝えるものはセキュリティのリスクをあわせて伝えていくべき」という考えがあるからです。

世界的にIT化が進み、それと同時に日々非常にたくさんの情報セキュリティの事故が起きています。サイバー攻撃をする人が悪いのはもちろんですが、セキュリティ事故の発生理由には、「実装がまずかった」という理由もあれば「設定ミス」という運用上のミスによる理由もあります。ITの開発に携わる人の責任も大きいのです。

私はIT業界に関わりがある仕事をしており、プログラミングに興味があります。ITの話題は好きです。ITに関わる人材が増えるのはとても良いことだと考えています。ただ、ITにふれるハードルが下がったのは良いのですが、肌感覚として世の中の人のセキュリティリテラシーがそれに追いついているのかどうか疑問に感じるところもあります。

エンドユーザーのシャドーITによってセキュリティにリスクが生じることもあるでしょう。そこにはExcel VBAによって生じるリスクも(極めて低いですが)ないとは言い切れません。

セキュリティの重要性を伝えられるのはITやプログラミングを教えられるような人たちです。アプリケーション開発の指導者もセキュリティを一緒に啓蒙できる存在であったほしいと、私個人として感じています。これは人に強制するものではなく私の単なる願いです。

まとめると

とても長くなりましたが、今回のRRさんのツイートに対してこの記事で私が言いたかったことをまとめると

  • RRさんの指摘には妥当性があるけど絡み方が失礼で避けるべきだった
  • 長期的に考えてVBAの立場を守るならセキュリティのことも考えるべき
  • そのことはITやプログラミングを人に伝える、もしくはそういう場を提供する者がしっかり啓蒙していくべき(という私の願望)

の3点です。

IT業界をずっと取材し続けている立場として、VBAが好きで触っている立場として思わずこんなことを書いてみました。

Twitterで誕生日に飛ぶ風船を再現するGoogle Chromeの拡張機能を作ってみた

f:id:tdyu5021:20210127000848p:plain

Twitterでは、プロフィールに誕生日を設定していると、その人の誕生日にホーム画面に風船が飛びます。あの仕掛けが個人的に好きなので、これをどのWebページでも再現するGoogle Chrome拡張機能を作ってみました。備忘録も兼ねてまとめてみます。

「毎日が誕生日」なGoogle Chrome拡張はこちら

このChrome拡張機能を使うとどんな感じになるのか。まずは、私が投稿した以下のツイート内の動画をご覧ください。

見ての通り、Webページを表示する度にTwitterの風船みたいなやつが飛んでくるというプログラムです。最初は楽しいけど、1分で飽きますね、これ…

まあ誕生日気分を味わいたい方にはぴったりなプログラムです。ちなみに本物のTwitterだと風船をタップすると割れるという仕掛けまでついているようですが、面倒なのでそこまではやりませんでした。

実際のJavaScriptのコード

というわけで最初にソースコードを載せます。Google Chrome拡張機能はHTML、CSSJavaScriptで作ることができ、しかもChromeストアに公開せず、自分のブラウザだけに適用するくらいならわずかな設定でできます。作り方は他のサイトみればすぐにわかりますので省略します(気が向いたらこのブログに書きます)

今回の拡張機能は既存の画面をいじるだけでなので使用するのはJavaScriptファイル(と風船の画像ファイル)だけです。その実際のコードが以下です。※jQueryで記述しているのでjQueryファイルを別途読み込んでいる前提です。

$(function(){
	//風船5種類
	img1 =chrome.extension.getURL('b1.png');
	img2 =chrome.extension.getURL('b2.png');
	img3 =chrome.extension.getURL('b3.png');
	img4 =chrome.extension.getURL('b4.png');
	img5 =chrome.extension.getURL('b5.png');
	imgArr = [img1,img2,img3,img4,img5];
	$('body').append('<div id="added"></div>');

	//画面の高さと幅を取得
	wh = $(window).height();
	ww = $(window).width();

	//風船オブジェクトの配列
	bl=[];
	for(var i = 0; i<30; i++){
		var startX=Math.floor(Math.random()*(ww/20))*20;
		var startY = wh+(Math.floor(Math.random()*10)*80);
		var ratio =1 / (Math.floor(Math.random()*5)+4);//Yが進む距離に対してXが進む距離の比率
		direction = 1-Math.round(Math.random())*2;
		bl[i]= new balloon(i,startX,startY,ratio,direction);
		bl[i].appear();
	}
});

function balloon(i,x,y,r,d){
	this.i=i;
	this.x=x;
	this.y=y;
	var dist1 =-8;
	var dist2 = Math.abs(dist1)*r*d;
	$('#added').append('<img id=image'+this.i+'>');

	//風船を出現させる
	this.appear = function(){
	  var target = $("#image"+this.i);
	  target.css({
	    position:'absolute',
	    top:this.y,
	    left:this.x,
	    zIndex:9999,
	    width:'80px'
	  });
	  var rnd = Math.floor(Math.random()*imgArr.length);
	  target.attr("src", imgArr[rnd]);
	  var xx = this.x;
	  var yy = this.y;

	  //風船を動かす
	  var si = setInterval(function(){
	    yy+=dist1;
	    xx+=dist2;
	   	target.css({
	      top:yy,
	      left:xx
	   	});
	  	if(yy<-200) clearInterval(si);
	  },20);
	};
}

作成のポイント

60行足らずの簡単なコードですが、ポイントをかいつまんで見てみます

①風船の画像を画面上に配置する

通常、HTML、CSS、JavaSciptなどで画像を読み込む場合は、参照先のディレクトリにもよりますが、もし相対パスなら

img1 ='img/b1.png'

上記のように書きますが、Chrome拡張機能を作る際には参照の仕方が異なります。

Chrome拡張機能を有効化するには必要なファイルをフォルダにまとめてアップロードするのですが、JavaScriptファイルと使用する画像ファイルが同じ階層にある場合、

img1 =chrome.extension.getURL('b1.png');

こんな感じで画像を参照させます。

次に、風船の画像ファイルを画面に配置するには、

(1)新しく<div>要素を作り、
(2)その中に風船の数だけ<img>要素を作成する

という感じで既存のHTMLの中に組み込む必要があります。

(1)の部分は上記のコードの中でこの部分です。appendメソッドでbodyタグ内の最後にまず<div>要素を付け足します。

$('body').append('<div id="added"></div>');

(2)に関しては、風船を格納する<img>要素を作ると同時に、画像を画面上で好きな位置に動かさなければならないので、 cssでpositionをabsoluteに設定します。最前面にくるようにzIndexプロパティも適当に大きな値にしておきました。

以下の部分で、付け足した<div>要素の中にappendメソッドで<img>要素を設定し、

$('#added').append('<img id=image'+this.i+'>');

以下の部分でスタイルを設定しています。

var target = $("#image"+this.i);
target.css({
   position:'absolute',
   top:this.y,
   left:this.x,
   zIndex:9999,
   width:'80px'
});

 風船オブジェクトとそのインスタンスをたくさん作る

今回は風船を大量に出現させ、またそれぞれが別の角度で別の場所で動いていくので、まず風船オブジェクトを定義し、そのインスタンスを生成して各風船(インスタンス)の位置やら進む角度や方向などを与えていくやり方がよさそうです。

この考え方を今回のコードで実行する際、骨子だけ作るとまず以下のような感じになるかと思います。 

for(var i = 0; i<30; i++){
  bl[i]= new balloon();
  bl[i].appear();
}

function balloon(){
	this.appear = function(){
		var si = setInterval(function(){
			$("#image"+[変数]).css({
				top:xxxxx,//天地の位置を動かす
				left:xxxx//左右の位置を動かす
			});
		},20);
	};
}

まずballonnという風船自体の関数オブジェクトを作り、その中に各風船を動かすappearというメソッド作ります。今回は適当に30個の風船を作ることにしたので、ループの上限を30にしてforループで回し、

bl[i]= new balloon();
bl[i].appear();

で各風船インスタンスを作成して動かします。

インスタンスを生成したときに風船オブジェクトに渡す情報(=new balloon();の引数)は以下です。

  1. 風船の通し番号
  2. 風船の左右の初期位置
  3. 風船の上下の初期位置
  4. 風船が進む上方向距離と左右方向距離の比率
  5. 風船の進む方向(左か右か)

 このうち4番目については、風船を動かすループの中で縦方向に動く距離に対して横方向に動く距離を比率をランダムに設定することで、風船が飛ぶ角度をバラバラにしており、その情報を渡すものです。もっといいやり方がないかなと思いつつ書いています。

だいたい要点はそんなところです。

この記事を書いた理由

 JavaScriptはたまにしか書かないので、結構関数オブジェクトとインスタンスの生成の書き方を忘れるんですよね。このプログラムも大した時間がかけてませんが、過去の自分のファイルを見ないと書き方わかりませんでした。

というかこの書き方で合ってるのか謎だし。別のプログラムでprototypeとかも使ってるけど、完全に雰囲気で書いている…(そしてその使い方はもう忘れた)

集中線を作成するしょうもないマクロの作り方【Excel VBA】

f:id:tdyu5021:20201120211445j:plain
以前Twitterに投稿したくだらないExcel VBAマクロシリーズです。需要はないだろうと思いますが、想像以上にバズったので、せっかくなのでその作り方を解説してみます。

集中線を作成するExcelマクロとは

まず以下のTwitterの投稿をご覧ください。集中線を作成するプログラムを実行した画面動画です。

「集中線」とは強調したものを目立たせるために放射上に周囲に線を引いた表現です。よくマンガで見かけるやつですね。

別にそこまでウケ狙いで作ったわけではないのですが、あまりにも線を増やし過ぎたせいで見た目のインパクトが強烈になり、

強調しすぎて他が見えなくなってるじゃねーかwww

そこ以外が見づれぇwwwww

などなど、いろんな方からのツッコミとともに結構バズってしまいました。中には「作り方を教えて下さい」というコメントもあったので、だいぶ今さら感はありますが、作成方法を紹介します。ソースコードは最後にまとめて掲載します。

円周上にオブジェクトを配置する

このプログラムは、実際のコードを順を追って解説する前に、まず「円周上にオブジェクトを配置する」方法を解説します。正直これさえわかれば後は簡単です。

円周上にオブジェクトを配置するとはどういうことかというと、例えば以下のような形を作成することです。

f:id:tdyu5021:20201017001726p:plain

これはオートシェイプを作成する「AddShapeメソッド」を、X座標、Y座標をずらしながら18回ループさせて円を配置したものです。ただ、通常のループの中で円の軌道をどうやって描けばよいでしょうか。

例えば直線上にオブジェクトを連続で配置していくには、X座標あるいはY座標の値をループで足していけばよいのですが、円周に沿って配置していく場合は単純にはいきません。

これは「円周上の座標の位置の求め方」を利用して対処します。その求め方は以下になります。

  • X座標…中心の座標+半径×Sinθ
  • Y座標…中心の座標+半径×Cosθ

f:id:tdyu5021:20201024225818p:plain

上記の式からわかるように、角度のθの値でX座標、Y座標は変化するので、θの値を変数にしてループさせればよいのです。サインθとコサインθの値は、VBAで標準で用意されている「Sin関数」と「Cos関数」を使用して求めます。この2つの関数は、Sin(X),Cos(X)のように記述し、Xに角度の数値の引数を入れて使用します。

角度をラジアンに変換する

ただ注意点として、このSin関数とCos関数の引数の角度の単位は、「ラジアン」を用います。そのため小学校の頃から慣れ親しんだ「度数」で管理したい場合、度数からラジアンへ変換するコードを挟まないといけません。

度数からラジアンへの変換自体は、

ラジアン=度数×(π/180)

で求められるので一見すると簡単ですが、なんと残念ながらVBAには円周率(π)を求める関数が存在しません。ですから円周率を何らかの方法で導く必要があります。

Excelのワークシート関数にはPi関数があるのでそれを使用してもよいですが、どうやら調べたところによると、アークタンジェントを求める「Atn関数」を用いて

Atn(1)×4

でも円周率(π)を求められるそうです。なぜ上記の式で円周率を求められるのか、私は理屈を説明できませんが、以下の記事に詳しく書いてあるので気になる方は読んでみてください。

excelmath.atelierkobato.com

 まとめとして、度数の変数をdegreeと置いた上で、Excel VBAで度数をラジアンに変換する記述は以下になります。

Pi = 4 * Atn(1) '//ここで円周率πを求める
radian = degree * (Pi / 180)

円周上にオブジェクトを配置するコード 

これを踏まえて、円周上に図形を配置するコードをまとめてみます。円の中心となるX座標、Y座標の変数をcenterX、centerYに、また半径の変数をRadiusにして、ひとまずここでは適当な数値を入れておきます。

配置する円については、上記サンプル画像のように18個配置したい場合は、360÷18=20、つまりループで20度ずつステップさせています。

Sub test()
    centerX = 200  '//円の中心となるX座標
    centerY = 200  '//円の中心となるY座標
    Radius = 100   '//円の半径
    For Degree = 1 To 360 Step 20
            Pi = 4 * Atn(1)
            radian = Degree * (Pi / 180)
            
            x = centerX + Radius * Sin(radian)
            y = centerY + Radius * Cos(radian)
                
            Dim s As Shape
            Set s = ActiveSheet.Shapes.AddShape(msoShapeOval, x, y, 20, 20)
    Next
End Sub

試しに上記のコードをコピペして実行してみてください。最初にお見せした画像のように円周上に配置されるはずです。

集中線を作成してみる

それでは今回の本題である集中線を実現するために、そのほか必要なポイントを見ていきましょう。

AddConnector メソッドで線を引く

まず集中線に必要な「線」はオートシェイプの「線(コネクタ)」を作成するメソッドであるAddConnectorメソッドを使います。

このメソッドは

Addconnector(Type、 [beginx]、 [beginy]、 [endx]、 [endy])

のように5つの引数を取ります。

[beginx]、 [beginy]はコネクタの開始位置のX座標、Y座標で、

[beginx]、 [beginy]は終了位置のX座標、Y座標です。

上記で円周上の座標の求め方を紹介しましたが、開始位置のX座標、Y座標は小さめの円周に、終了位置のX座標、Y座標は大きめの円周に沿って配置すれば線を放射上に配置することができます。

試しに、上に記した円周上に配置するコードのうち、Radiusの値を小さめに設定した上で該当部分を以下のように書き換えてみてください。

x = centerX + Radius * Sin(radian)
y = centerY + Radius * Cos(radian)
x2 = centerX + (50 + Radius) * Sin(radian)
y2 = centerY + (50 + Radius) * Cos(radian)

Dim s As Shape
Set s = ActiveSheet.Shapes.AddConnector(msoConnectorStraight, x, y, x2, y2)

線(コネクタオブジェクト)が放射上に並ぶはずです。

そのほか集中線っぽくするためのチューニング

ただ、放射上に線を配置しただけでは旭日旗みたいな模様になり、集中線っぽくはなりません。これを解消するために以下の調整をかけています。

  1. 線ごとに開始位置を変える
  2. 線と線の間隔は等間隔でなくバラバラにする
  3. 線ごとに先の太さを変える

サンプルコードを例に取ると、今回のマクロでは、

1については変数Radiusの値をランダムに生成

2については変数Degreeをランダムに生成してForループで大量発生

(このループの回数が多くなればなるほど線が増えるので集中度合いが増します)

3については、コネクタオブジェクトのLine.Weightプロパティで太さをランダムで1か2にする、というふうにしています。

楕円オブジェクトを中心に線を伸ばす

集中線自体の作成は以上です。ただ最初のTwitter内の動画でもおわかりの通り、このマクロははじめに楕円オブジェクトを作り、もしワークシート上に楕円オブジェクトが見つかれば、その円周上を開始X座標、開始Y座標にして線を生成するという処理を行っていますので、その部分をご紹介します。

この処理を実現するには、

  • 楕円オブジェクトと中心のX座標・Y座標
  • 楕円オブジェクトの直径(今回は楕円の横幅を想定してプログラムを作っています)

の情報を取得しておきます。中心の座標はシェイプオブジェクトのTopやLeft、Width、Heightを用いて簡単に求められます。以下の画像の通りです。

f:id:tdyu5021:20201024212419p:plain

真円ではなく楕円状に配置する

この記事のはじめに、円周上にオブジェクトを配置する例を示しましたが、その円は完全な真ん丸(真円)でした。これを楕円にする場合、単純に拡大したい方向に任意の値を掛け算すればOKです。

一応書いておくと、座標を求める部分を以下のようにすると、横幅の直径が縦幅の直径の2倍の楕円ができます。

x = centerX + Radius * Sin(radian)*2
y = centerY + Radius * Cos(radian) 

 今回の集中線では、線の開始位置をプロットする円の直径は横幅(=X軸方向)を基準にしています。

そのため、楕円の縦の直径が横の直径と異なる場合は、その比率を縦幅の方に加味してあげる必要があります(例:楕円の縦幅が横幅の1/2なら、X座標を求める式に÷2をする)

この処理を書いておけば縦長、横長どちらの楕円にも対応できる集中線を作成できます。

補足:この集中線の欠陥

実はこのコードには重大な欠点があります。画面をある程度縮小していても集中線で画面を覆えるようにするために、中心から線の開始位置までの長さと中心から線の終了位置までの長さを1000というかなり大きめの値に設定しています。

ただその長さを固定にしてしまうと、線の終端座標がワークシートの左端や上端をオーバーしてしまう可能性があり、そうなるとうまく対応できないのです。

ちなみにシェイプオブジェクトでTopとLeftが0未満になった場合は、自動で0として扱われるため、線の長さはそのままに線の終端座標が0を基準として図形が勝手に動かされてしまいます。

この対策として「最初に楕円を置いた場所によって上側、左側にある線の長さを変える」という方法もなくはないのですが、うまくいかなかったので今回は妥協しました。とりあえず、「線の終端座標のX座標、Y座標が0未満になった場合は値を0にする」という処理で逃げましたが、それをやっても以下みたいな感じになってしまい、完璧に対処はできていません。

f:id:tdyu5021:20201024222412p:plain

実際にできあがったコード

最後に今回作成したコードを紹介します。実用される方はまずいないと思いますが、試しにご自身のワークシートなどで遊んでみてもらえれば幸いです。

Sub createLines()
    Randomize
    Dim shp As Shape
    Dim s As Shape
    Dim flg As Boolean
    Dim target As Shape

    '//ワークシート内の楕円を探す
    For Each shp In ActiveSheet.Shapes
      If shp.AutoShapeType = msoShapeOval Then
          flg = True
          Set target = shp
          Exit For
      End If
    Next shp
    '//楕円が見つからなかったらマクロ終了
    If flg = False Then Exit Sub

    w = target.Width
    h = target.Height
    targetLeft = target.Left
    targetTop = target.Top
    
    '//楕円の中心のX,Y座標
    centerX = targetLeft + w / 2
    centerY = targetTop + h / 2
    
    ratio = w / h
    '//楕円を削除
    target.Delete

    For i = 0 To 360
        Radius = w / 2  '//楕円の半径
        Degree = Int(Rnd * 360) '//ランダムに角度を決定する
        '//↓↓↓角度をラジアンに変換する
        Pi = 4 * Atn(1)
        radian = Degree * (Pi / 180)
        '//↑↑↑
        
        gosa = Int(Rnd * 70) '//線を開始位置をばらつかせるためランダムな誤差を発生
        
        '//線の開始地点、終了地点を円状にプロット
        x = (Radius + gosa) * Sin(radian)
        y = (Radius + gosa) * Cos(radian) / ratio
        x2 = (Radius + 1000 + gosa) * Sin(radian)
        y2 = (Radius + 1000 + gosa) * Cos(radian) / ratio
        
        '//楕円の位置を基準にする
        x = centerX + x
        x2 = centerX + x2
        y = centerY + y
        y2 = centerY + y2
           
        '//↓線を伸ばした先が画面を越える場合の対策
        If x2 < 0 Then '//もし画面の最左端を超えたら
            x2 = 0     '//とりあえずx座標は0に
        End If
        If y2 < 0 Then '//もし画面の最上端を超えたら
            y2 = 0     '//とりあえずy座標は0に
        End If
        '//↑
        
        Set s = ActiveSheet.Shapes.AddConnector(msoConnectorStraight, x, y, x2, y2)
        weightNum = Int(Rnd * 2) + 1
        s.Line.Weight = weightNum
        s.Line.ForeColor.RGB = RGB(0, 0, 0)
    Next
End Sub

ワークシートのイベントプロシージャをアドイン化する方法【Excel VBA】

f:id:tdyu5021:20200919230157p:plain

Excel VBAで記述したマクロをどのExcelファイルからでも使えるために「アドイン(.xlam)」で保存する方法があります。ですが、シートモジュールのコードはアドイン化できません。そうなるとそこに記載したイベントプロシージャをアドイン化したい場合はどうすればよいでしょうか。不完全ながら暫定的な方法を発見したのでメモ代わりにまとめてみます。

ワークシートのイベントプロシージャとは

Excel VBAビギナーであれ、そこそこ慣れている方であれ、ワークシートのイベントプロシージャを使っている方は多いと思います。

ワークシートのイベントプロシージャとは何かを念のため説明すると、例えばワークシートやセルなどに対して何らかの操作が行われたことをトリガーに実行されるプロシージャのことです。通常は「シートモジュール」に記載されます。

以下の画面は、ワークシート上のセルなどの内容に変化があった場合、メッセージボックスを表示するマクロの例です。

f:id:tdyu5021:20200919231049p:plain

ワークシートのイベントプロシージャもアドイン化したい

このシートモジュールに書かれたイベントプロシージャはアドイン化することはできません。当然です。特定のシートモジュールに書かれているということは、コードがそのシートにだけしか適用されないからです。

ではどうすればワークシートのイベントプロシージャをどのExcelファイルからでも使えるようにできるでしょうか。実は私も100%の答えにたどり着けていないのですが、浅はかな知識なりに暫定的な方法を考えたので以下に紹介します。

私が思いついた方法

今回のアドイン化にあたって、一定の制約を設けています。

まず、あるExcelファイルを開いたとき、そのファイルに存在するすべてのワークシートでも、また開いたあとに追加したワークシートでも動くイベントプロシージャをつくるのは少々面倒なので、とりあえずExcel ファイルを開いたときの「アクティブなシートで」使えるようにするという方法に限定して考えます。

以下、その方法です。ここでは開くファイルををBook.xlsx、アドインのファイルをaddin.xlamと想定します。

  1. addin.xlamのクラスモジュールで、ワークシートのオブジェクトを生成しておき、そこでイベントプロシージャを記述しておく
  2. Excelファイル(ここではBook.xlsx)を開いたとき、上記の1で生成したワークシートオブジェクトのインスタンスとして、開いたExcelファイル(Book.xlsx)のアクティブシートオブジェクトを格納する
  3. 2で記した「ワークシートオブジェクトにExcelファイルのアクティブシートを格納する」というプロシージャをAuto_Openにして自動化する。

初心者の方やクラスモジュールを使われたことがない方は、上記を読んでもしかしたら「??」と思ったかもしれません。そのような方にご説明しておくと、実はイベントプロシージャはクラスモジュールに書くことができます。上記はそれを利用したものです。

クラスモジュールを利用されたことがない方であれば、「ユーザーフォームのイベントプロシージャだったらフォームモジュールに」、「ワークシートのイベントプロシージャだったらシートモジュールに」というふうに書いてきたかと思います。

ワークシートのイベントプロシージャをクラスモジュールに書く方法

話は少し脱線しますが、今回のテーマとなるコードを初心者の方でも理解できるように、そもそも「イベントプロシージャをクラスモジュールに書く」というのがどういうことなのかをここで説明しておきます。

そのため先述した

  1. addin.xlamのクラスモジュールで、ワークシートのオブジェクトを生成しておき、そこでイベントプロシージャを記述しておく
  2. Excelファイル(ここではBook.xlsx)を開いたとき、上記の1で生成したワークシートオブジェクトのインスタンスとして、開いたExcelファイル(Book.xlsx)のアクティブシートオブジェクトを格納する
  3. 2で記した「ワークシートオブジェクトにExcelファイルのアクティブシートを格納する」というプロシージャをAuto_Openにして自動化する。

こちらのステップが何を言っているかわかっている方はここは読み飛ばしてください。

ワークシートのイベントプロシージャの例

例えば、イベントプロシージャの例として、「シート上でセルを選択し直したら『選択セルを変更』というメッセージを表示する」というコードを考えてみましょう。Excel初心者の方でも10秒でかけるコードです。以下をシートモジュールに書くはずです。

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    MsgBox "選択セルを変更"
End Sub

これをシートモジュールを使わずに書くと以下のようになります。標準モジュールとクラスモジュール(クラス名はデフォルトのClass1としておく)にそれぞれ以下のように記述します。

標準モジュール

Dim c As New Class1
Sub test()
    c.setting ActiveSheet
End Sub

クラスモジュール 

Public WithEvents sh As Worksheet
Sub sh_SelectionChange(ByVal Target As Range)
    MsgBox "選択セルを変更"
End Sub
Sub setting(ByVal s As Worksheet)
    Set sh = s
End Sub

もちろんこのコードを記述しただけではシート上のイベントプロシージャは発動しません。上記の中の”test”というプロシージャを実行すると、実行時のアクティブシートでイベントプロシージャが実行されるようになります。

ただ”test”という標準モジュールをいちいち実行してからでないとイベントプロシージャを利用できないのは煩わしいので、プロシージャ名のtestを“Auto_Open”にしておけば、ファイルを開いたときに自動でこのイベントプロシージャが利用できるようになります。

WithEventsキーワード

上記に記した、クラスモジュールを用いたイベントプロシージャ生成を可能するのが「WithEvents」キーワードです。これはクラスモジュール内のみで使用でき、ワークシートオブジェクト、ワークブックオブジェクト、ユーザーフォームオブジェクトなどなど、イベントプロシージャが用意されているオブジェクトの変数を宣言でき、それにイベントを割り当てられるというすぐれものです。

クラスを利用して、「ひな形」としてのワークシートオブジェクト(上記のサンプルでは"sh"という変数)を宣言しておいて、その後変数"sh"に、開いたExcelファイルのアクティブシートを格納した(=settingというメソッドに引数"activesheet"を渡した)という流れです。

こうしてアドインはでき上がる・・か?

ここまで来たらおわかりかもしれませんが、イベントプロシージャをアドイン化するアプローチとして、シートモジュールを使わずにクラスモジュール(と標準モジュール)にコードを記載すれば、今回の目的のものができ上がるだろうというのが私の狙いです。

なぜか動かない!その理由は?

ここまで来たらできるはず!と思い、上記の"test"のモジュール名を”Auto_Open”に変更した上で実際にこのファイルをxlam形式で保存。

そしてアドインを有効にしていざ試してみたのですが、その結果、うまくいきませんでした……

実行してもエラーにはならず原因がわかりません。通常のExcelファイルとして開くとうまくいくのに、アドインにするとなぜかうまくいかない。

正直、この理由がわからずずっと悩んでいたのですが、いろいろ調べるうちに動かなかった原因がようやく判明しました。その理由は単純なものでした。

標準モジュール内では以下のように、インスタンスを生成したオブジェクトにActiveSheetを格納するという処理を書いているわけですが、

Sub test()
    c.setting ActiveSheet
End Sub

どうやらExcelファイルを開くとき、「アドインファイルを読み込んだ時点では開いたExcelファイルのワークシートオブジェクトはまだ存在していない(開かれていない)」ようです。

なので、ここで記載した“ActiveSheet”は、開いたExcelのワークシートではなくどのワークブックのものでもないため、不明なオブジェクトになってしまうのです。

なので結論を言うと私の考えた上記の方法でアドイン化を行うのは無理であることがわかったのですが、対症療法的な措置として実現する方法がありました。

つまり、アドインを読み込んだ時点でまだ開いたExcelファイルのシートが存在しないなら、それが存在するのを待ってからアドインのコードを実行すればよいという対処方法です。

具体的にはAuto_Openのプロシージャの中に

c.setting ActiveSheet

記載するのでなく、それを別のプロシージャに書いておき、Auto_Openではそのプロシージャを時間差で実行するようにするという方法です。コードに書くと以下の通りです。

Dim c As New Class1
Sub Auto_Open()
    Application.OnTime Now + TimeSerial(0, 0, 2), "test"
End Sub
Sub test()
    c.setting ActiveSheet
End Sub

これは”test”というプロシージャをアドインファイルを読み込んでから2秒後に実行するというものです。このように変更したところ無事に新しく開いたExcelファイルでシートモジュールのイベントプロシージャが動きました。

ちなみにApplication.OnTimeメソッドは指定した時間にマクロを実行するためのものです。TimeSerial関数は、引数で指定した数字を時刻で示す関数です。上記の例だと、0,0,2なので0時0分2秒です。

最後に

上記でも運用上は問題ないかもしれませんがなんかすっきりしません。これをどうにかする方法はないのかなーと思っていたのですが、Twitter上で得た情報によるとワークシートのイベントプロシージャをアドイン化するれっきとした方法はあるらしいです。(具体的な方法は結局のところわかりませんが…)

気が向いたらもう少し研究してみようと思います。

数式や関数を打ち間違えるとニコニコ動画風に煽ってくるExcel VBAマクロの作り方

f:id:tdyu5021:20200525220235p:plain

先日Twitterに「Excel操作中に関数や数式を打ち間違えたときニコニコ動画風に煽ってくるクッソうざいマクロ」というネタVBAを投稿したところ、想像以上にバズりまくってビビってます。汎用的に使えるものではないですし公開する気はさらさらなかったのですが、実態は初学者でもわかる簡易なプログラムですし、ネタが一人歩きしたことで私がすごい技術を使っていると誤解されたくもないので、中身を公開することにしました。ソースコードは最後にまとめて載せます。

話題のクッソウザいマクロがこちら

まずは以下のツイートをご覧ください。

見ての通り、関数の名前を打ち間違えたことでエラーを起こしてしまうと大量の煽りコメントで罵倒されるという精神的にやられるプログラムです。

まさかこんなにバズると思わなかった…というくらいバズってしまい、前回の集中線VBAと同様Togetterにまとめられただけでなく、まとめサイト(俺的ゲーム速報)にもまとめられてしまうという事態に…

togetter.com

jin115.com

(※2020年5月27日追記:ねとらぼにも取り上げられました。これは結構嬉しかった)

nlab.itmedia.co.jp

 そんなこんなでいろいろ引用RTやらリプやらでコメントをいただく中で、

「こんな知識と技術の無駄遣い、最高にステキです

だとか

「UZEEEEEEEE(褒め言葉)

とか言ってくださるコメントもあり、それはそれで嬉しいのですが、私は特にVBAに詳しいわけでもないですし、別に実態としては大したことはしてないです。

というわけで、その中身を1つずつ紹介していきます。

関数の打ち間違いを検出する

まず入力間違いの判定です。ここは何も難しいことをしていなく、ただIsError関数を使って「数式や関数がエラーになればプロシージャを呼び出す」ということをしているだけです。シートモジュールに以下のコードを記述しています。

Private Sub Worksheet_Change(ByVal Target As Range)
    If IsError(Target.Value) Then
        Call 今回のマクロ
    End If
End Sub

テキストボックスを生成する

多分、Excel VBAをまともに使っている人の大半はセルを操作したりするので、オートシェイプやテキストボックスを扱うことはあまりないんじゃないかなと思います。ここはとっつきにくい部分ですが、テキストボックスをアクティブなシートに生成するには以下のように、ShapeオブジェクトのAddTextboxメソッドを使用します。

(説明のために実際のコードから少々簡略化して記載しています)

Sub test()
    Dim tbx As Shape
    Set tbx = ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, 1000, 10, 100, 30)
End Sub

1つの目の引数はテキストボックス上の文字の方向です。以降の引数は

第2引数:テキストボックスの左端位置
第3引数:テキストボックス上端位置
第4引数:テキストボックスの幅
第5引数:テキストボックスの高さ

です。つまり上記を実行すると、画面左から1000、上から10の位置に、幅100高さ30のテキストボックスが生成されます。

ちなみに、コメントは画面右からスタートして左に流れますが、残念ながら開始位置を厳密に制御するのはVBAでは難しいです。そのユーザーがExcelの画面のどの列まで表示させているかどうかは、画面の表示倍率やPCのディスプレイのサイズによって異なるからです。

というわけでこのプログラムでは、適当に「アクティブセルのLeftプロパティの数値からプラス400くらいしたところ」と決め打ちしています。この数字には特に意味はありません。

なので関数の打ち間違いを起こしたセルがA列とかだったらコメントは中央辺りから流れてしまいます。

テキストボックスのスタイルを整える

テキストボックスを生成しましたが、このままだと塗りつぶしや線などがデフォルトの状態に設定されているのでそれらを調整します。今回適用したテキストボックスのスタイルは以下です。

Sub test()
Dim tbx As Shape
    Set tbx = ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, 10, 10, 1000, 30)
    With tbx
            .Fill.Visible = msoFalse    '//塗りつぶしの有無
            .Line.Visible = msoFalse    '//線の有無
            .TextFrame2.MarginTop = 0       '//テキストボックスの上の余白
            .TextFrame2.MarginRight = 1.8   '//テキストボックスの右の余白
            .TextFrame2.MarginBottom = 0   '//テキストボックスの下の余白
            .TextFrame2.MarginLeft = 2   '//テキストボックスの左の余白
            .TextFrame2.VerticalAnchor = msoAnchorMiddle    '//垂直方向の配置(ここでは上下中央)
            .TextFrame2.HorizontalAnchor = msoAnchorNone    '//水平方向の配置(ここでは設定無し)
            .TextFrame2.TextRange.Font.Size = 18     '//フォントサイズ
            .TextFrame2.TextRange.Font.NameFarEast = "MS Pゴシック"   '//フォントの種類
            .TextFrame2.TextRange.Font.Bold = True    '//ボールドかどうか
    End With
End Sub

(今思うと何でテキストボックスの余白まで設定しているかは謎)

あとはテキストボックスのTextFrame2.TextRange.Characters.Text プロパティで、流すコメントのテキストを入れておきます。今回のプログラムでは、20種類の決め打ちのコメントを配列で用意しており、それをランダムに入れています。

Twitterからのコメントでは「Average関数以外でもその関数に合った間違いを指摘してくれるのか?」的なコメントがありましたが、もちろんそんな高度なプログラムではありません 笑

もう一度いいますが、コメントは全部決め打ちです。

テキストボックスを左に動かす

いったん説明のために簡略化していますが、テキストボックス(tbx)を左に流すコードは以下のように記述しています。ループの回数は適当です。

For i = 1 To 500
   tbx.IncrementLeft -2
   Application.Wait [Now() + "0:00:00.01"]
Next i

IncrementLeftプロパティは、Shapeオブジェクトの左位置を指定するものです。これをループさせることでテキストボックスを左に2ごと移動させています。ただし、この記述だけだと一瞬でループの回数分移動してしまうのでアニメーションのように動きません。そこで以下の記述を追加します。

Application.Wait [Now() + "0:00:00.01"]

Waitメソッドはプログラムの処理を一瞬止めるためのメソッドです。これをループ中に仕込み、0.01秒止めることでテキストボックスの移動が連続して行われているように見せることができます。

テキストボックスが一番左に来たら、そのテキストボックスの位置を再度画面右側に移し、かつ中身のコメントも別のものに変更します。簡略化しますが、ここは以下のような処理です。

If tbx.Left < 10 Then
    tbx.Left = [画面の右側の方の位置をランダムに]
    tbx.TextFrame2.TextRange.Characters.Text = [用意したコメントからランダムに]
End If

大量のコメントを別々のスピードで流す

ここまでコードを簡略化して紹介してきましたが、このままのコードではテキストボックスは1つしか流れてきません。たくさんコメントを流すにはどうするかというと、配列変数を用いて変数tbxをForループでたくさん生成すればよいのです。

合わせて、それぞれのテキストボックスに異なる高さを設定しておけば画面のいろいろな高さから出てきます。

テキストボックスの流れる速さをそれぞれ変えるのは簡単で、先ほどご紹介したIncrementLeftプロパティの引数の数値をたくさん生成したテキストボックスごとに変えればよいのです。

これを行うには配列変数tbx()に対し、IncrementLeftの引数もspeed()のように配列変数にしてランダムな数字をいれておきます。tbx(0)にはspeed(0)で、tbx(1)にはspeed(1)で左に進むようにする、という感じです。

終わったらテキストボックスをすべて消す

ShapeオブジェクトにはTypeプロパティというものがあり、オートシェイプなのかテキストボックスなのかなど、Shapeの種類を取得できるプロパティがあります。テキストボックスはmsoTextBoxなので、テキストボックスを全て削除するプログラムは以下のように記述しています。

Sub deleteAll()
    Dim shp As Shape
    For Each shp In ActiveSheet.Shapes
        If shp.Type = msoTextBox Then
            shp.Delete
        End If
    Next shp
End Sub

終わりに

以上です。内容としてはざっとこんな感じです。

このプログラムは直接シートモジュールに、Changeイベントによる間違い検知のコードを記載していますし、何よりコメントが流れ始める位置はユーザーの表示環境によって異なり、そこを吸収できるように汎用的に作られてはいません。

あと、コメントが画面の最左端まで流れて来たとき、それより左には進めないので、そこでコメントを消さざるを得ないのも個人的には気になっています。

(おわかりかと思いますが、ニコニコ動画では、コメントが画面から見切れるまで流れ続けています)

このジョークプログラムは本当はアドイン化したかったのですが、なんか方法があるんですかね、これ。

シートモジュールのイベントプロシージャはクラスモジュールに書けるようですが、アドイン化できるかはいまのところ私の知識ではわかりません。

というわけで、いろいろ中途半端ですが、以下にコードを載せておきます。まぁ絶対に誰も使うことはないと私は確信していますが、ご使用の場合はすべて自己責任でお願いします…笑

'****************************************
'*シートモジュール
'****************************************
Private Sub Worksheet_Change(ByVal Target As Range)
    If IsError(Target.Value) Then
        Call test
    End If
End Sub
'****************************************
'*標準モジュール
'****************************************
Dim tbx(8) As Shape
Dim speed(8) As Integer
Sub test()
    Randomize
    
    '//コメントを全部配列に(数はいくつでもOK)
    arr = Array _
    ( _
    "だせえwwwwwwwwww", _
    "これ完全Excel初心者だろ", _
    "これは無能", _
    "こんなの同じ会社にいたら嫌だわ", _
    "wwwwwwwwwwwwwww", _
    "普通そんな間違いしないだろwww", _
    "これはありえない", _
    "ダサすぎてワロタ", _
    "俺でもさすがにAverageは打てるぞ", _
    "関数も使えないとか草", _
    "Average関数もわからないとかwwwwwwww", _
    "こいつは間違いなく無能", _
    "Average関数で間違えるやつ初めて見た", _
    "これがゆとり教育の弊害か・・・", _
    "ワロタ", _
    "えっそこ間違える!?", _
    "Excelよりも英語の勉強をやり直したほうが", _
    "そもそもこの表は何なんだよ", _
    "wwwwwwwwwwwwwwwwwwwww", _
    "wwwwwwwwwwwwwwwwwwwwwww" _
     )

    '//コメントの配列の長さを取得
    arrLen = UBound(arr)
    
    '//数字を適当に並べる
    hArr = Array(2, 5, 4, 1, 3, 7, 6, 8, 9)
    
    '//アクティブセルの左位置と高さを取得
    cpl = ActiveCell.Left
    cph = ActiveCell.Height
    
    For i = 0 To 8
        n = hArr(i) * 30
        '//テキストボックスを生成
        Set tbx(i) = ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, cpl + 400, n, 400, 30)
        
        '//テキストボックスのスタイルをもろもろ設定
        With tbx(i)
            .Fill.Visible = msoFalse
            .Line.Visible = msoFalse
            .TextFrame2.MarginTop = 0
            .TextFrame2.MarginRight = 1.8
            .TextFrame2.MarginBottom = 0
            .TextFrame2.MarginLeft = 2
            .TextFrame2.VerticalAnchor = msoAnchorMiddle
            .TextFrame2.HorizontalAnchor = msoAnchorNone
            .TextFrame2.TextRange.Font.Size = 18
            .TextFrame2.TextRange.Font.NameFarEast = "MS Pゴシック"
            .TextFrame2.TextRange.Font.Bold = True
        End With
        '//コメントが進むスピード(距離)
        speed(i) = Int(Rnd * 4) + 3
        
        '//配列からテキストをランダムに選んでテキストボックスに入れる
        txt = arr(Int(Rnd * arrLen))
        tbx(i).TextFrame2.TextRange.Characters.Text = txt
    Next i
    
    For ii = 1 To 600
        For j = 0 To 8
            '//テキストボックスを左に移動
            tbx(j).IncrementLeft -speed(j)
            
    '//テキストボックスが左の方まで来たらもう一度右の方に戻してコメントも入れ直す
    If tbx(j).Left < 10 Then
        tbx(j).Left = cpl + Int(Rnd * 300) + 200
        tbx(j).TextFrame2.TextRange.Characters.Text = arr(Int(Rnd * arrLen))
    End If
        Next j
        Application.Wait [Now() + "0:00:00.01"]
    Next ii
    
    '//終わったらテキストボックスを全部消す
    Call deleteAll 
End Sub
Sub deleteAll()
    Dim shp As Shape
    For Each shp In ActiveSheet.Shapes
        If shp.Type = msoTextBox Then
            shp.Delete
        End If
    Next shp
End Sub

 

JavaScriptとCanvasでブラウザに星空を描く

f:id:tdyu5021:20191119005505p:plain

 CanvasJavaScriptの練習として星空を描いてみました。初心者がゼロから作ったものなのでわりと簡単です。

ソースコード

先にソースコードを載せておきます。HTMLはCanvasタグを設けるだけでOK。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">
<script type="text/javascript" src="script.js"></script>
<title>夜空</title>
</head>
<body onload="draw()" style ="margin:0">
<canvas id = "canvas"></canvas>
</body>
</html>

 JavaScriptは以下です。

function draw(){
    canvas = document.getElementById("canvas");
    c = canvas.getContext("2d");
    //canvasの領域を画面サイズに
	canvas.width = document.documentElement.clientWidth;
	canvas.height = document.documentElement.clientHeight;

	//グラデーションの設定と描画
    var g = c.createLinearGradient(canvas.width/2, 0, canvas.width/2, canvas.height);
	g.addColorStop(0, '#000');
	g.addColorStop(1, '#021a69');
	c.fillStyle = g;
    c.fillRect(0,0,canvas.width,canvas.height);

	var maxX = Math.floor(canvas.width/10);
	var maxY = Math.floor(canvas.height/10);

	//流れ星*************************************	

	//translateする前の状態を保持
	c.save();

	var x00 = 100;
	var y00 = 100; 
	var len = 300;
	var dia0 = 2;
	c.translate(600,100);
	c.rotate(-(18 * Math.PI / 180));
	c.fillStyle = "#ffffff"; 
	c.beginPath();
	c.moveTo(x00,y00);
	c.lineTo(x00+len,y00+dia0*2);	
	c.lineTo(x00,y00+dia0*2);
	c.closePath();
	c.fill();	
	c.beginPath();
	c.arc(x00,y00+dia0,dia0,0, Math.PI*2, false);
	c.fill();	
	c.closePath();

	//前の状態を戻す
	c.restore();

	//光り輝く星*************************************		
	for(var i=0; i<15; i++){
		var x1 = Math.floor(Math.random()*maxX)*10;
		var y1 = Math.floor(Math.random()*maxY)*10;
		var dia = Math.floor(Math.random()*10)+15;
		c.lineWidth=0;
		c.strokeStyle = "#ffffff"; 
		c.fillStyle = "rgba(255, 255, 255, 0.1)"; 
		//外側の円
		c.shadowColor = "#ffffff";
		c.shadowOffsetX = 0;
		c.shadowOffsetY = 0;
		c.shadowBlur = 3;
		c.beginPath();
		c.arc(x1, y1, dia/2.5, 0, Math.PI*2, false); 
		c.closePath();	
		c.fill();
		//内側の円
		c.beginPath();
		c.fillStyle = "#ffffff"; 
		c.arc(x1, y1, dia/5, 0, Math.PI*2, false);
		c.closePath();	
		c.fill();
		//横棒
		c.beginPath();
		c.moveTo(x1-dia,y1);
		c.lineTo(x1,y1-1);	
		c.lineTo(x1+dia,y1);
		c.lineTo(x1,y1+1);
		c.closePath();
		c.fill();
		//縦棒
		c.beginPath();
		c.moveTo(x1,y1-dia);
		c.lineTo(x1-1,y1);	
		c.lineTo(x1,y1+dia);
		c.lineTo(x1+1,y1);
		c.closePath();
		c.fill();
	}

	//通常の星(円だけ)*************************************
	var a = [1,1,1,1,1.5,1.5,2,2.5];	
	c.fillStyle = "#ffffff";
	c.shadowColor = "#ffffff";
	c.shadowOffsetX = 0;
	c.shadowOffsetY = 0;
	c.shadowBlur = 3;
    for(var i = 0; i< 300; i++){   
		var x = Math.floor(Math.random()*maxX)*10;
		var y = Math.floor(Math.random()*maxY)*10;
		var n = Math.floor(Math.random()*a.length);
		var radius = a[n];
		c.beginPath();
		c.arc(x, y, radius, 0, Math.PI*2, false);
		c.closePath();
		c.fill();
	}

	//輝く星(円+十字)*********************************
	for(var i=0; i<20; i++){
		var x = Math.floor(Math.random()*maxX)*10;
		var y = Math.floor(Math.random()*maxY)*10;
		
		//円
		var dist = Math.floor(Math.random()*3)+1;
		c.fillStyle = "#ffffff";
		c.shadowBlur = 4;
		c.beginPath();
		c.arc(x, y, dist, 0, Math.PI*2, false); 
		c.closePath();
		c.fill();

		var diff = dist*(2/3);
		//横棒
		c.lineWidth=0.8;
		c.strokeStyle = "#ffffff"; 
		c.beginPath();
		c.moveTo(x-(dist+diff),y);
		c.lineTo(x+dist+diff,y);
		c.closePath();
		c.stroke();
		
		//縦棒
		c.beginPath();
		c.moveTo(x,y-(dist+diff));
		c.lineTo(x,y+dist+diff);
		c.closePath();
		c.stroke();
	}
}

このスクリプトで覚えたこと(個人的備忘録)

Canvas初心者の私が、これを作成するにあたって初めて知った知識を以下に備忘録兼学習メモ的に記します。

線形グラデーションを描く

これはcreateLinearGradientメソッドというものを使います。

createLinearGradient(x0, y0, x1, y1)という形を取り、引数1と引数2はグラデーションの開始地点のxとy座標、引数3と引数4はグラデーションの終了地点のxとy座標です。

画像を回転させる

描いた星空の中に、斜めの線で表現した流れ星があります。これは真横に書いた図形をrotateメソッドで斜めにしています。このメソッドは引数を1つ取り、ラジアンで回転させる角度を指定します。ただ、このメソッドは対象のオブジェクトを中心に回転させるのでなく、画面の左上を起点に回転させてしまうので、そのままでは使いづらいです。そこで一緒に使うのが次のに紹介するtranslateメソッドです。

座標位置をずらす

Canvasは、デフォルトでは座標の左上が(0,0)ですが、translateメソッドを使うと座標の位置をずらすことができます。これをrotateメソッドと組み合わせて使うと、rotateで回転させる座標を左上起点でなく、回転させる自分自身にすることができます。

※注:なのですが、今回のコードはそうなっていません。rotateしたとき、ほどよい場所に流れ星が来るよう適当な数字を入れているだけです。

canvasの状態を記憶して元に戻す

ちなみにtranslateメソッドで座標をずらすと以降が座標が全部ずれてしまうので、saveメソッドでtranslate前のCanvasを保持しておき、流れ星を作ったあとにrestoreメソッドを使って元に戻しています。こうすれば以降の記述はtranslateメソッドの影響は受けません。

ぼかしをかける

夜空に輝く星を作るのに欠かせないのが、ぼかしです。shadowBlurプロパティを指定するとオブジェクトにぼかしをかけられます。

Canvasのサイズを画面幅に合わせる

私がいままで作ってきたプログラムでは、CanvasのサイズはCSSやHTML側で固定値にすることが多かったのですが、今回は開いた画面いっぱいに描画したいので、可変にする必要があります。画面幅に合わせてCanvas領域を指定する方法を探したところ以下のように書けばOKのようです。

canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;

今回手を抜いたところ

星空はランダムに描画されますが、面倒だったので流れ星は場所が決め打ちになっています。その点は手を抜きました。

小さい星はただ丸とか単純な十字を書いただけですが、光り輝く星は割ときれいに作れたかなと個人的に気に入ってます。

Canvasでアート系はもっと作ってみたいです。(目指すはジェネレーティブアート)