syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

Android Javaのアサーション

昨年2018年は転職したこともあって、一番よく使った言語はJavaでした。とはいっても、ほとんどはAndroidJavaであり、サーバーサイドやデスクトップで使われている正式なJavaとはいささか性質が異なります。

JavaC/C++C#と違ってプリプロセッサをサポートしないのですが、そのせいで一番困るもののうちのひとつはアサーション(表明)です。アサーションは「契約による設計」(design by contract) を実現・実践するために有効なプログラミング手法であり、呼び出し元が保証すべき事前条件や、呼び出し先が保証すべき事後条件をコード中に記述し、条件が満たされなかった場合はプログラムを続行せずに中断します。一方、リリースビルド(本番環境)でアサーションが無効になっているときは、引数として渡した式(表明したい条件)が評価されずに実行コストゼロとなることが好ましく、そのためC/C++C#ではコンパイルオプションに応じて有効/無効が切り替わるプリプロセッサマクロやConditional属性付きメソッドによって、ゼロオーバーヘッドのアサーションを実現しています。逆に言うと、リリースビルドではアサーションの条件式は評価されないため、必ず実行されないと困るような式(システムに対して副作用のある式)は記述しないようにします。

#include <cassert>
// NDEBUG シンボルが定義されていない場合、assert マクロが有効になる。
// アサーションが有効な場合は assert マクロが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、abort 関数が実行される。
static void test(const int ary[], size_t length) {
    assert(ary != nullptr);
    for (size_t i = 0; i < length; ++i) {
        printf("%d\n", ary[i]);
    }
}

C/C++標準のassert()マクロは、表明が失敗 (assertion failure) するとabort()関数でプログラムを強制終了しますが、C#Debug.Assert()メソッドは表明が失敗した場合に無視してプログラムを続行することもできます。MSVCのCRTマクロ、_ASSERT()_ASSERTE()に近い挙動です。

// DEBUG シンボルが定義されている場合、Assert メソッドが有効になる。
// アサーションが有効な場合は Assert メソッドが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、エラーメッセージが表示される。
static void Test(int[] ary) {
    System.Diagnostics.Debug.Assert(ary != null);
    foreach (int x in ary) {
        System.Console.WriteLine(x);
    }
}    

Javaにはバージョン1.4で assert ステートメントが追加されたのですが、Javaアサーションコンパイルオプションではなく実行時オプションで有効/無効を切り替えます。JITコンパイラによる最適化がうまく機能すれば、無効時にはゼロコストとなることが期待できます。

// アサーションが有効な場合は assert ステートメントが実行される。無効な場合は引数の評価ごと無視される。
// 条件が満たされず、アサーションが失敗したとき、java.lang.AssertionError がスローされる。
static void test(int[] ary) {
    assert ary != null;
    for (final int x : ary) {
        System.out.printf("%d\n", x);
    }
}

上記の例に限れば、仮にリリースビルドでnullが渡されたとしても、途中でjava.lang.NullPointerExceptionがスローされることでプログラムは中断されますが、nullを有効値として許可しないメソッドであるということをコード上で明らかにするためにもアサーションは有用です。Java 7以降であれば、java.util.Objects.requireNonNull()を併用するとなお良いです。ちなみにJSR 305の@Nonnullアノテーション (javax.annotation.Nonnull) はメソッドインターフェイスのドキュメント化に役立ちますが、すべての処理系で使えるわけではないのが残念です。

Android Javaの場合

しかし、Android 4.xまでの仮想マシン Dalvik では、アサーションの有効/無効を切り替えるシステム プロパティの debug.assert は信頼性がなく(無視されることがある)、さらにAndroid 5.0以降の仮想マシン ART (Android Runtime) では未実装とのこと。
Android StudioJava言語組み込みのassert文を使おうとすると、以下のような静的チェックツール (lint) による警告が出ます。

Assertions are not checked at runtime. There are ways to request that they be used by Dalvik (`adb shell setprop debug.assert 1`), but note that this is not implemented in ART (the newer runtime), and even in Dalvik the property is ignored in many places and can not be relied upon.
Instead, perform conditional checking inside `if (BuildConfig.DEBUG) { }` blocks. That constant is a static final boolean which is true in debug builds and false in release builds, and the Java compiler completely removes all code inside the if-body from the app.
For example, you can replace `assert speed > 0` with `if (BuildConfig.DEBUG && !(speed > 0)) { throw new AssertionError() }`.
(Note: This lint check does not flag assertions purely asserting nullness or non-nullness; these are typically more intended for tools usage than runtime checks.)
https://code.google.com/p/android/issues/detail?id=65183

つまり現在のAndroidでは、Java言語組み込みのassert文はまともに使用できません。
代替として、上記 lint では java.lang.AssertionError を明示的にスローするコードを書くことが提案されていますが、正直そんなまどろっこしいことやってられません。

Android開発環境が自動生成するクラスBuildConfigのboolean定数フィールドBuildConfig.DEBUGを使って分岐すれば、確かにコンパイラ最適化によりリリースビルドではゼロコストとなることが期待できるのですが、分岐&例外スローを逐一書くのがダルいので、自分はJUnitライクのアサーションユーティリティクラスと静的メソッドを作って対処しています。個人的には分岐やメソッド呼び出しのオーバーヘッドよりも、分岐&例外スローを逐一書くことでノイズとメンテナンスコストが増大することのほうが懸念だからです。なお、表明したい式を直接記述してbooleanで受け取るのではなく、ラムダ式で表現して遅延評価することにより、リリースビルドでの実行時コストを削減する方法もありますが、ラムダ式を使わない場合と比べてやはりコード上のノイズが増えます。

import java.util.function.*;

public class MyAssert {
    public static final boolean DEBUG = true;

    public static void assertTrue(boolean condition) {
        if (DEBUG && !condition) {
            throw new AssertionError();
        }
    }

    public static void assertTrue(BooleanSupplier expression) {
        if (DEBUG && !expression.getAsBoolean()) {
            throw new AssertionError();
        }
    }
}

// 利用例。
MyAssert.assertTrue(ary != null);
MyAssert.assertTrue(() -> ary != null);

AndroidJavaは本物のJavaではなくJavaモドキでしかない、としばしば揶揄されますが、こういった非互換な仕様や動作には実際かなりウンザリさせられます。Oracleがブチ切れて訴訟を起こしたくなる気持ちも理解できます。自分にとってC/C++の一番嫌いなところは、処理系依存の未定義動作があまりに多すぎることですが、本来のJavaC#はそういった処理系依存の未定義動作が極力排除されていることが最大の魅力のうちのひとつとなっています。仮にもJavaを名乗るのであれば、そういった根本的な設計思想を曲げるようなことをすべきではありません。これは技術者の倫理 (engineering ethics) に関わる問題です。

C++でstaticクラス

C#には「staticメンバーだけを定義して、実体化は許可しない」ようなクラスを簡潔に定義する手段があります。

public static class MyHelper
{
  public static int Add(int x, int y)
  { return x + y; }
}

staticクラスは状態を管理しない純粋ヘルパー(アルゴリズム)を定義するのに便利です。代表的な例でいうと、System.Mathクラスが挙げられます。
なお、staticクラスからはサブクラスを派生させることもできません。

ちなみにJavaでもクラスをstaticキーワードで修飾することができますが、意味合いがまったく違います。C#のほうがキーワードに即していて直感的だとは思いますが。

C++/CLIおよびC++/CXでは以下のように書くことでC#のstaticクラス相当を記述できます。

public ref class MyHelper abstract sealed
{
public:
  static int Add(int x, int y)
  { return x + y; }
};

しかし、従来の標準C++では、staticクラスを記述する文法は直接サポートされていません*1
せいぜいできることと言えば、以下のようにデフォルトコンストラクタとデストラクタを隠ぺいして実体化を禁止するくらいしかなく、C#と比べて分かりやすさはともかく美しさに欠けます。
また、派生を禁止するにはC++11以降のfinalキーワードを使う以外ありません。

class MyHelper final
{
private:
  MyHelper() {}
  ~MyHelper() {}
public:
  static int Add(int x, int y)
  { return x + y; }
};

MSVCにはネイティブC++でもabstractキーワードを使えるように言語拡張が施されているので、abstract sealedまたはabstract finalを指定することでstaticクラスを記述することができるのですが、Clangなどでは使えません。移植性の高いコードを書きたい場合は注意が必要です。

*1:C++では必ずしもクラスに関数を定義する必要はなく、クラスに属さない名前空間レベルの関数を定義することもできるので、これまでstaticクラスのサポートが重要視されてこなかったのかもしれません。

無修正画像のリクエストを受けたとき、どう対処・回答すべきか

三次元のお話

自分には縁のない話ですが、一般論として。
恋人に「裸の自撮り写真を送ってくれ」「他の誰にも見せたりしないから」などとお願いされても、はっきりと拒絶すべきですね。そしてそんなことを臆面もなく言ってくるリテラシーの低い野郎とは即刻別れるべきです。将来表を歩けなくなってもかまわないなら話は別ですが。
つい忘れがちですが、インターネットは全世界につながっています。一見プライバシーが保護されているように見えるメールも例外ではありません。いまや画像はもちろん動画でさえ、誰でも簡単にデータをアップロード・共有できますが、同時にクリックひとつで全世界に公開できてしまいます。ネット上に裸の画像をばらまかれて永久に消せない汚点を残すような、いわゆるリベンジポルノにつながるだけでなく、刑法175条のわいせつ物頒布罪(わいせつ電磁的記録記録媒体頒布罪*1)に問われる可能性もあります。

二次元のお話

では、二次元のイラストや漫画に関してはどうでしょうか?
やはり日本国内では三次元同様「無修正はわいせつ物とみなされてアウト」になります。
pixivなどのSNSサイトで、R18カテゴリーのエッチなイラストや漫画をアップロードされている方は注意が必要です(自分を含めて)。
自分で撮った写真や自分で描いた絵などを、自分だけがアクセスできる個人端末のハードディスクや光学メディアに保存すること、つまり無修正の元データの単純所持自体には法律的に問題ありません。そうでないと作品の創作や制作ができませんので。もちろん児童ポルノは除きます。児童ポルノは無修正だろうが修正済みだろうが、単純所持ですらアウトです*2児童ポルノ、ゼッタイダメ。

しかし、たとえ自作であろうとも、無修正画像を不特定多数に公開・譲渡するとなると、前述の刑法175条に抵触します。公開対象の多い少ないにかかわらず、メールやSNSのファイル添付、ストレージサービスなど、共有目的でインターネットにアップロードすること自体が危険だと考えてください*3
某ポルノサイトでは海外のサーバーに無修正データをアップロードすることで日本の法律を潜り抜けようとしていますが、限りなく黒に近いグレーだということもお忘れなく。

よって、他人から「○○の無修正画像をもらえませんか?」などというリクエストを受けても、安易に譲渡してはいけません。

個人的意見

これは絵描きとしての個人的な意見ですが、せっかく描いたものに、モザイクやぼかし、ベタ/ホワイトで修正なんてしたくないんです。同人誌を発行するときや、SNSサイトに公開するときに、ときおり修正が弱いと指摘されることがあり、しぶしぶ手直ししていますが、内心では引っかかるものがあります。別にゾーニングができていればいいじゃないか。むやみに隠そうとするから逆におかしなことになるんです。むしろ修正したほうが余計にわいせつ感が増すと感じるのは自分だけではないはず。滑稽ですね。ゾーニングができていない(年齢詐称による抜け道があり、事実上誰でも閲覧できる)ことのほうがよほど問題です。

具体的に何をどの程度修正すれば「わいせつ物」とみなされないか、に関しても、法律で明確に定められているわけではありません。警察のさじ加減ひとつで決まってしまいます。修正は出版社・印刷会社・イベント主催者や創作者の自主規制でしかありません。でも捕まりたくはないので、安全側に自主規制せざるを得ない。ジレンマですね。その自主規制も年々強くなっているようで、業界全体が萎縮しているように感じられます。昔はゴールデンタイムに放送されるアニメでも乳首くらいは余裕だった*4というのに……

*1:「記録」が連呼されていますが、どうやら誤植ではなく法律上の正式名称らしいです。ややこしいですね。

*2:非実在青少年」とやらの話は、ここではツッコミません。あしからず。

*3:たとえ個人的なバックアップ目的だとしても、クラウドサーバーに無修正画像をアップロードするのは避けるべきです。

*4:昨今の深夜アニメの謎の光や自主規制マークは、円盤の購買意欲をそそるための差別化戦略でもあるのかもしれませんが。

Windows 10 1709/1803でキャレットの点滅が止まる

Windows 10 1709 (Fall Creators Update) には、エディットボックスなどのキャレット(縦棒のカーソル)の点滅が5秒程度で止まってしまうバグがあるようです。確かキャレットはシステムタイマーでOSが点滅させているはず*1ですが、フォーカスしてから5秒くらい経過すると点滅が止まってしまいます。キャレットに独自の形状を使って自前で描画しているMIFESでも点滅が停止します。1703 (Creators Update) 以前ではこんな現象は発生していませんでした。1803にも同様のバグがあるようです。

キャレットの点滅は、現在のフォーカスがどのUI要素にあるのか、またアプリ(のUIスレッド)が生きているのかどうかを知ることのできる重要なインジケーターのひとつなので、これが止まってしまうとかなり困ります。この問題は、MFCアプリを開発していたときに気づきました。一瞬アプリ側のバグもしくは設計ミスかとも思いましたが、ありとあらゆるWin32アプリで発生するのでOS側に問題があることは確実です。なおFirefoxは自前のタイマーでキャレットを点滅させているようで、OSのバージョンに関係なく点滅は無限に続きます。OneNote 2016は30秒くらい経過すると止まりました。

しかし、あろうことか窓の杜の某ライターは、キャレットの点滅が止まってしまうのを「仕様変更」だとうそぶいています。これは仕様変更じゃなくてただのお粗末なバグです。なぜ公式発表もないのに、れっきとしたメディアのライターがいい加減なことを書くのでしょうか。

さらに無理やり点滅を継続させるサードパーティ製ツールの紹介までしていますが、こんなくだらないツール作るヒマがあったらMSに不具合報告すべきですね。なんでも日曜大工でDIYすればいいってもんじゃないです。自動車の構造や計器に安全基準不適合の不具合や設計ミスがあったらリコール対象でしょう。不具合に気付いたら勝手に自分で改造して対応したり、その場しのぎの改造方法をユーザー間で共有したりするべきではなく、社会全体のことも考えて国土交通省に報告すべきです。ちなみにWindows 10ではエンドユーザーでもフィードバックHubで不具合報告できるので、おかしな動作に気がついたら個人でもどんどん報告すべきですが、多数の意見・要望が集まらない限りMSは動こうとしません。無視・黙殺されるのがオチです。

Windows 10の品質問題

Windows 10は特に従来のWin32デスクトップまわりの品質が明らかに低下しているように思われます。1709はGetPixel/SetPixel関数のパフォーマンスデグレードもやらかしやがったので、まったく良い印象がありません。1703は2018年10月でサポート期限が切れますが、1709はスキップして1803にすることも検討しています。
それにしても今のWindowsは、すでにある機能を破壊・劣化させてまで追加する価値のある機能など、なにひとつ提供できてないと思います。むしろ余計な新機能のほうがユーザーエクスペリエンスの邪魔をしているくらいです。Windows 7/8.1同様の快適性を得るために、いったいいくつの機能を無効化したか知れません。既存機能の破壊と劣化はApple同様、リリーススケジュールばかり優先して品質確保をおろそかにしているツケがまわってきているだけですが、頻繁な大型アップデートによる新機能追加なぞまったく望んでもいないユーザーが、なぜそこで不利益を被らなければならないのか。極めて疑問です。

*1:点滅間隔はOSのシステム設定で変更できるほか、SetCaretBlinkTime() APIが用意されています。

FirefoxでGoogle検索結果から特定のサイトを除外する

安全・快適なインターネットライフのために、Google検索結果から不快・不正なサイト(NAVERまとめとか)は除外したいですよね。

Firefox Quantumより前のバージョンでは、「Hide Unwanted Results of Google Search」というアドオンを使っていました。このアドオンの素晴らしいところは、ワイルドカード正規表現で除外したいサイト・ページを柔軟に指定することができた点です。しかし、Quantum (v58以降) では仕様変更により、古いアドオンが使えなくなりました。「Hide Unwanted Results」はその後更新されておらず、Quantumに対応していないので非常に残念です。

Quantumに更新した後、現在は「Personal Blocklist (not by Google)」を使っています。もともとGoogle Chrome向けの公式アドオンとして公開されていた「Personal Blocklist (by Google)」を、Firefox向けに書き直したものだそうです。

ただ、残念ながらこのアドオンは「Hide Unwanted Results」と違い、ドメイン/ホスト単位でしか除外できません。たとえばQiita (qiita.com) から特定のユーザー*1のページだけを除外する、ということはできないわけです。

Googleの検索設定もしくは検索オプションで除外指定できれば一番いいのですが、おそらくGoogleは広告収入を断たれたくないのでフィルタリングには消極的なのだと思われます。

*1:Qiita自体は有用で参考になりますが、他のユーザーをバカにした不快なコメントを連発している傲慢なユーザーや、コメント欄でたびたびケンカ・炎上させているようなユーザーのページは検索結果にすら表示させたくないです。

Windowsセキュリティ認証とネットワークパスワード

Windowsで共有フォルダーにアクセスする場合、セキュリティ認証のためのパスワード入力を求められることがあります*1

しかし、PCを再起動/シャットダウンするなどして、ログオンセッションが終了すると、再度入力が必要になってしまいます。

コマンドプロンプトでセキュリティ認証を実行する場合、以下のようにnetコマンドを使うことができます。

net use \\<IPアドレスもしくはホスト名>\<共有名> /user:<ユーザー名> <パスワード>

再入力の手間を省く方法として、コマンドをバッチファイルに書き込んでおいて、バッチファイルをスタートアップに登録する手法もあるのですが、パスワードを平文でテキストファイルに書き込むのはセキュリティ上のリスクがありますね。

代わりに「資格情報マネージャー」(Credential Manager) を利用するとよさそうです。

www.mtecweb.com

Windows 10の場合もコントロールパネルからアクセスできます。

ちなみにTortoiseGitなどもWindowsのセキュリティ認証を使用しますが、あろうことか認証ダイアログで誤ったパスワードを入力してしまった場合でも、その誤ったパスワードが資格情報マネージャーに記録されてしまいます。以後、認証ダイアログが出ずに接続が延々と失敗し続けてしまう現象が発生します。誤ったパスワードを削除する場合、コントロールパネルの資格情報マネージャーから各情報セットを削除する必要があります。

*1:同一ユーザー名で同一パスワードの場合、パスワード入力なしに透過的にアクセスできます。

Visual C++コンポーネントの復旧

Visual Studio 2015 Update 3がインストールされているWindows 7 x64環境において、Python Tools for Visual Studioを2017年1月版に更新した後、Visual C++プロジェクトを含むソリューションファイルを開くと、

インストール コンポーネントがないため、プロジェクト 'XXX' を読み込めませんでした。修正するには、以下の選択をして Visual Studio セットアップを起動してください:
Install Visual C++ 2015 Tools for Windows Desktop

というエラーメッセージが出るようになりました。VC++プロジェクトは「利用不可」と表示されます。

しかし、Visual C++のプロジェクトテンプレート群はインストールされていて、Visual Studioのバージョン情報にもVisual C++コンポーネントは表示されます。どうやら、何らかのファイルが破損するなりして、VC++コンポーネントが正常に認識されていない模様。

[プログラムと機能]からVisual Studioのインストールウィザードを使って[修復]インストールしても改善しません。
[変更]インストールでコンポーネントのインストール状態をチェックしたところ、なぜか[プログラミング言語]→[Visual C++]配下のコンポーネントのチェックがすべて外れていました。

チェックを入れ直してインストールを実行すると、問題が解消されました。

一度「利用不可」状態になってしまったプロジェクトは、.suoファイルにアンロード状態が記録されてしまうようなので、コンテキストメニューから[プロジェクトの再読み込み]を実行して状態をリセットすればOK。

たぶんPython Toolsを更新したときに一部のVC++コンポーネントが勝手に削除されてしまったのだと思われます。ひどいバグですね。