- 投稿日:2021-12-24T17:53:00+09:00
MagicOnionでサービスから別のサービスを呼び出す
C#でgRPCの通信を実現するにあたって、MagicOnionは非常に便利なライブラリです。クライアントサイドとサーバーサイドの言語を同じにできるというのは、保守性等に限らず勉強コストや人員配置コストでもいい点がたくさんありますよね。 利用例はUnityによるゲーム開発が多いですが、私は普通の業務アプリ開発での採用を現在検討しています。 使ってて特に楽しいのが1リクエストで1レスポンス返ってくるようなよくある単発のAPI(本稿ではサービスと呼びます)の実装です。MagicOnionでは以下のように普通のメソッドとして自然に書けます。 SampleService.cs // サーバーとクライアントで共有する定義プロジェクトにISampleServiceを定義する // このソースはサーバープロジェクトで実装 // クライアント側はISampleService経由でawait SampleCallAsync()して呼び出す public class SampleService : ServiceBase<ISampleService>, ISampleService { // サービスの処理本体 // UnaryResult<T>はサービスの戻り値をとるためのTask-likeな型 // awaitが不要な場合も多々あるので、CS1998警告はプロジェクト全体で抑圧する public async UnaryResult<string> SampleCallAsync() { return "Sample"; } } ここでよくあるのが、別のクラスとして定義したサービスをどう呼ぶの?という疑問です。当然、役割の違うものは別クラスとして定義したいですし、すでにサービスとして定義済みのものを呼び出したくなるケースってよくありますよね? ログイン用に、ユーザーIDを送信して存在確認をするサービスを定義 フレンド登録処理で、他人のユーザーIDの存在確認処理として流用したい 送信データをもとに従業員の作業日報を登録するサービスを定義 出勤処理、退勤処理、休憩登録処理、などいろんなところで細かく自動で日報登録したい 本稿では、あるサービスから別のサービスを呼び出す方法について紹介したいと思います。 サービスの定義 説明のために適当なサービスを定義します。以下サンプルコードから名前空間の定義は省略します。 まず、呼び出すサービスをIHelloWorldとします。 IHelloWorld.cs public interface IHelloWorld : IService<IHelloWorld> { // "Hello World! (日時)"を返すメソッド UnaryResult<string> HelloWorldAsync(); } 呼び出されるサービスをIGetServerDateTimeとします。 IGetServerDateTime.cs public interface IGetServerDateTime : IService<IGetServerDateTime> { // DBサーバーの日時を返すメソッド UnaryResult<DateTime> GetServerDateTimeAsync(); } 別サービスの呼び出し方 2つの関係は「IHelloWorldはIGetServerDateTimeに依存する」という形になります。ということはつまり、IGetServerDateTimeをIHelloWorldにコンストラクタインジェクションするというのがきれいな方法でしょう。 呼び出される側の具象クラスの実装 呼び出される側は普通に実装するだけです。 GetServerDateTime.cs public class GetServerDateTime : ServiceBase<IGetServerDateTime>, IGetServerDateTime { public async GetServerDateTimeAsync() => MyDatabaseManager.GetDate(); } 呼び出す側の具象クラスの実装 呼び出す側には工夫が必要です。といっても、注意点は通信コンテキストを同じにするという点だけです。 GetServerDateTime.cs public class HelloWorld : ServiceBase<IHelloWorld>, IHelloWorld { // コンストラクタインジェクション private readonly IGetServerDateTime _getServerDateTime; public HelloWorld(IGetServerDateTime getServerDateTime) : base() { // note: base()は自身のコンテキストにdefaultを設定しているだけ _getServerDateTime = getServerDateTime; } public async UnaryResult<string> HelloWorldAsync() { // コンテキストを引き継ぐ ((ServiceBase<IGetServerDateTime>)_getServerDateTime).Context = this.Context; // あとは普通に実装する var dt = await _getServerDateTime.GetServerDateTimeAsync(); return $"Hello World! {dt}"; } } 重要なのが_getServerDateTimeへのContextプロパティの設定です。Contextはコンストラクタ時点ではnullであり、このクラスでは実際のメソッドまで到達しないと設定されていません。そのためインジェクションしたものを使うタイミングでの設定が必要になります。 また、ContextはServiceBase<T>のプロパティなのでIService<T>からはアクセスできません。そのためキャストをしてから設定します1。 Contextが不要なら設定せずに呼び出してもエラーにはなりませんが、そもそもそのような処理はMagicOnionのサービスである必要がないので普通のクラスにするかstaticなユーティリティメソッドとして取り回すべきでしょう。 実際の開発時ではいちいちキャストするのは面倒くさいので以下のような拡張メソッドを定義するのが楽だと思います。nullの場合にどうするかは要検討ですけども。 MyMagicOnionExtension.cs public static class MagicOnionExtensions { public static void SetContext<T>(this IService<T> src, ServiceContext context) where T : IServiceMarker { if (src is null) throw new ArgumentNullException(nameof(src)); ((ServiceBase<T>)src).Context = context; } } DIコンテナの設定 最後にDIコンテナの設定をします。MagicOnionのサーバーサイドをgithubに記載の手順で作っている場合、ASP .Net Core gRPCサービスプロジェクトになっているはずです。これには標準でDIコンテナがついていますので、Startup.csのConfigureServicesメソッドでサービスの登録を行います。 Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); services.AddMagicOnion(); // services.AddTranseint(毎回生成するタイプ)で具象クラスを登録 services.AddTransient<IGetServerDateTime, GetServerDateTime>(); } ここで、利用者側であるIHelloWorldは登録不要です。 本来、IService<T>はクライアントから処理を呼び出したときにMagicOnionがうまいこと具象クラスを取り出してくれます。なのでDIコンテナに内部で登録されているのかと思いましたがそうではないようです。実際に、AddTranseintの行を削除するとHelloWorldAsyncを実行するタイミングで「IGetServerDateTimeが見つからない」という実行時例外になります。別サービスに注入されるものは明示的にDIコンテナへの登録が必要、ということを覚えておきましょう。 ただし、いちいちコンテナへの登録は面倒なのでSourceGeneratorを使ってコンテナへの登録コードを具象クラス定義時に自動実装するのが楽でよいと思います。 暴論でいうとDIコンテナを使わずにHelloWorldAsyncの中でGetServerDateTimeクラスをnewすることもできますが、密結合になるので良くない方法なのは言うまでもありません。 まとめ あるサービスから別のサービスを利用するには以下手順を行います。 呼び出される側は普通に作る 呼び出す側はコンストラクタインジェクションし、利用時にContextを引き継がせる Startup.csでDIコンテナに呼び出される側のインターフェイスと具象クラスを登録する なお、あるストリーミング処理から別のサービスを呼び出すこともできます。ServerStreamingContextおよびDuplexStreamingContextは内部でgetter-onlyなServiceContextプロパティを公開しているので、これを使って同じ手順で呼び出すことが可能です。 ただし、ストリーミングクラスにグループへ参加するサービスを定義したからといって、ストリーミングやサービスから別のストリーミングへの参加メソッドを呼ぶようなことはしないほうがいいでしょう。素直にクライアントから参加するほうが、グループのin-outが明確になるため、システム全体の見通しが良くなります。 ServiceBase<T>でDIすると今度はTであるサービスインターフェイスに定義したGetServerDateTimeAsync()が呼べなくなる ↩
- 投稿日:2021-12-24T14:22:51+09:00
C#でカノジョをつくる(25日目)
お疲れ様です。たなしょです。 まずはここまで25日間、駄文に付き合っていただきありがとうございました。 読書の方々がいたからこそ、思い付きで始めた企画ですがプログラミングをしてブログを書き続けることができました。 今日は最終日なので今までの振り返りができればいいなと思います。 Cシャープを勉強してみる デスクトップアプリを作りたいということでC#を一から勉強してみました。 Javaや他の言語の経験があるので基本的な文法はすぐにキャッチアップすることができました。 今までの記事でも散々書いてきましたが記法は綺麗でとても読みやすいのとVisualStudioというIDEの力もあってとてもスムーズに開発することができました。 このような感じに{}を書くのが新鮮でとても見やすかったです。 class CollectionInitializable : IEnumerable { public void Add(string item) { Console.WriteLine($"{item} Added"); } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } } あとLINQなどの特殊な文法も学びました。 LINQに関してはSQLを書いてる感覚でデータセットを処理できるのはわかりやすいなと思う反面、SQLを書いた経験がない初学者には理解するのが大変な文法なのでないのかと思いました。 実際私も1冊目の参考書では理解できず、2冊目の参考書である程度理解することができました。 VisualC#でアプリを作る VisualC#でアプリを作るのはとても楽しかったです。 フォームで簡単なアプリを作り、電卓をつくたりしながらVisualC#でのアプリ開発を学びました。 各パーツへの値の渡し方がわかればデスクトップアプリを作ることができるのは感動しました。 電卓はラベルを用意して各ボタンがクリックされたタイミングで計算式をラベルに入れるだけで作れました。 カノジョ(チャットボット)を作る VisualC#になれたところでカノジョを作りました。 返信用のスーパークラスを作成してサブクラスとして各種返信用のクラスを作成して、返信ごとの機能を追加していきました。 始めはランダムな辞書によるあいさつとオウム返ししかできなかったアプリが、ランダム辞書を作成し、単語とそれに連なる返信を切り分けるParseクラスを作成することで、ユーザーの発言がパターンにマッチした場合はパターンに沿った返信をしてくれるようになりました。 またパターン辞書に数値を入れることで機嫌値という概念を設けました。 特定のキーワードをユーザーが発言すると機嫌値が上下して喜怒哀楽まで表現できるようになり、これには感動しました! さらにコミュニケーションをできるようにした MeCabというライブラリはPythonを触っていた時に耳にしたことはあるのですが今回使うのは初めてだったので使ってみて機能を知ることができました。 形態素解析をしてユーザーの発言を切り分けて名詞と判別したものをパターンとして登録し、パターンを連なる発言をユーザーの発言から学習するようにさせたことでたくさん会話すれば色々なキーワードにあった会話を楽しむことができるようなりました。 さらにテンプレート辞書を作成することで%noun%という文字列をユーザー発言内にある名詞に置き換えることでさらに会話の幅が広げることができました。 完成したカノジョです! 今回書いたコードもGitHubにアップしましたのでぜひ参考にしてみてください! 今後の課題 まだ発展途上のアプリなので少ないですが今後は時間を見つけて以下の課題に取り組んでいきたいと思っています。 ・ チャットボットからデスクトップマスコットへ変更 ・ Grep機能などのその他機能の追加 最後に 25日間お付き合いいただきありがとうございました! 優良な記事が多いQiitaの中でこのような駄文を垂れ流してしまってごめんなさい。 今回は当初の目標であるカノジョ(アプリ)を作ることを達成することができました。 ものを作ることが楽しいというモチベーション一本で何とかここまで続けることができました。 作っていて初めからWindowsフォームからがちがち作っていったほうがもっと良いアプリが作れたのではないかという反省点もありますが、それは今後の開発で生かしていこうと思います。 年末は低レイヤーの世界にでも引きこもってOSっぽいものでも作ろうかと思います。 あとは息抜きでGoでCLIツールを作ったりPHPでWebアプリでも作りながら家に引きこもってると思います。 ではではみなさん良い年末をお過ごしください! ありがとうございました!
- 投稿日:2021-12-24T14:21:23+09:00
C#でカノジョをつくる(24日目)
お疲れ様です。たなしょです。 今日は昨日に引き続きテンプレート用の返信する処理を作成していきます。 テンプレート返信用クラスを作成する 1.ParseItemオブジェクトを1つずつ取り出して、ユーザーの発言にキーワードが含まれていた場合リストに追加します。 public override string Response(string input, int mood, List<string[]> parts) { List<string> keywords = new(); foreach (string[] morpheme in parts) { if (Analyzer.KeyWordCheck(morpheme[1]).Success) { keywords.Add(morpheme[0]); } } 2.キーワードの単語が1個以上あり、テンプレート辞書にcountと同じキー値が存在する場合、処理に入ります。 if ((count > 0) && (Cdictionary.Template.ContainsKey(Convert.ToString(count)))) 3-1.count変数と同じ数の%noun%をもつ、テンプレート文字列のリストを取得します。 var templates = Cdictionary.Template[Convert.ToString(count)]; 3-2.取得したリストからランダムに1個のテンプレートを抽出します。 string temp = templates[rdm.Next(0, templates.Count)]; 3-3.正規表現として%noun%を登録します。 Regex re = new("%noun%"); 4.キーワードのリストからキーワードを取り出して、テンプレートの%noun%をキーワードに置き換えます。 foreach (string word in keywords) { temp = re.Replace(temp, word, 1); } return temp; 5.キーワードの単語が0、またはキーワード認定の単語の数が合わない場合はランダム辞書からフレーズを抽出します。 return Cdictionary.Random[rdm.Next(0, Cdictionary.Random.Count)]; 各返信用モジュールに形態素解析の結果を取得するパラメーターを追加する 今まで作成してきた返信クラスの引数に形態素解析用のパラメータを追加します。下記はスーパークラスに追加したときのものです。 public virtual string Response(string input, int mood, List<string[]> parts) 本体クラスにテンプレート返信用のオブジェクトを実装する private TemplateResponder _res_template; 応答フレーズにもテンプレート返答を追加します。 else if (num < 7) _responder = _res_template; 実行してみるとカノジョの文言のバリエーションがさらに増えて、会話らしい会話ができるようになりました! ダイアログを見てみるとそこそこ会話できるようになっています。 最後に テンプレート辞書を用いた会話ができました。 これで少しは楽しく会話できそうです! 明日は最終日なので今までの総括をします。
- 投稿日:2021-12-24T10:06:41+09:00
【C#】SQLの接続文字列をApp.configファイルから取得する Visual Studio
はじめに C#学習1か月目の超初心者です。 つまったところをまとめました。 やりたいこと 直にうっているSQLの接続文字列を、 App.Configファイルに記述し、そこから呼び出す。 若干、エラーがでたのでそこも記述します。 App.Configファイルへの記述 App.Config //私の場合 バックスラッシュになってるところは本来は¥マークです。 <connectionStrings> <add name="DB1" connectionString="Data Source=CTS-SQL2014\SQL2014;Initial Catalog=Prac_TableName;Persist Security Info=True;User ID=ID;Password=password" providerName="System.Data.SqlClient"/> </connectionStrings> Catalog=Prac_TableNameやら、IDやらパスワードは適当に打ち換えています。 参照設定の追加 Visual Studioのソリューションエクスプローラーの「参照」から、 「参照の追加」で、「System.Configuration」を追加します。 ソリューションエクスプローラーの「参照」に追加されてることを確認してください。 接続文字列の呼び出し 上部に、「using System.Configuration;」を追記します。 Form1.cs //上記は省略 //SQLと接続するための記述 using System.Data.SqlClient; //以下追記します。 using System.Configuration; Form1.cs //ここで接続文字列を呼びだし? string ConnectionStr = ConfigurationManager.ConnectionStrings["DB1"].ConnectionString; DataTable dt = new DataTable(); //SQLに接続する SqlConnection cn; //ここで接続文字列を使用しています。 cn = new SqlConnection(ConnectionStr); cn.Open(); 発生したエラー Form1.csにSQLの接続文字列を記載していたときは、 データソース名に、¥が入っており、それだとエスケープシークエンス?になるので、 ¥を二つ記載してました。 しかしながら、App.Configファイルで同じように記載すると、 呼び出される接続文字列には¥が4つも入っていました。。 そのため、App.Configファイルには、本来通り¥マーク一つ記載するようにしたところうまくいきました。
- 投稿日:2021-12-24T09:49:41+09:00
Dart(C#)のEnumを少しでもJava/Kotlinっぽい感じにしてやる
JavaやKotlinを使っていると、Enumに値を持たせるという便利な機能が当たり前だと思うのですが、DartやC#のEnumではそれができません。 なので、Enumを拡張して擬似的に値を持てるようにしてやりました。同じ制約を持つC#もEnumの拡張ができるので、同様に書けると思います。 Dart 2.15です void main() { for (var type in ArticleType.values) { print('${type.val.title} ${type.val.description}'); } } /// Javaっぽくしたいenum enum ArticleType { news, // ニュース sport, // スポーツ entertainment, // エンタメ fashion, // ファッション anime, // アニメ } /// Enumの値用のクラス class Article { const Article({ required this.title, required this.description, }); final String title; final String description; } /// 拡張してEnumの値を持てるようにしてやる extension ArticleTypeEx on ArticleType { Article get val { switch (this) { case ArticleType.news: return Article(title: 'ニュース', description: '最新のニュースです'); case ArticleType.sport: return Article(title: 'スポーツ', description: 'スポーツのニュースです'); case ArticleType.entertainment: return Article(title: 'エンタメ', description: '流行りのエンタメ情報です'); case ArticleType.fashion: return Article(title: 'ファッション', description: 'トレンドファッションアイテム'); case ArticleType.anime: return Article(title: 'アニメ', description: '今期覇権アニメです'); } } } メリット enumの値の定義が漏れた場合、コンパイルエラーとなり気づくことができる フィールドを増やすたびに拡張を生やさなくていい。switch文はここだけで済む。保守しやすい
- 投稿日:2021-12-24T07:31:29+09:00
オライリーの特約書店をBlazorでマップ化したウェブサイトを作成した話
概要 みんな大好き「オライリー」の特約書店をマップとして可視化するウェブサイトをBlazor WebAssemblyで作ったので紹介します。 元は.NET 5時代に作ったものなのですが、.NET 6にマイグレーションをした上で少しだけ手を加えたりしたので改めてネタとして紹介しようかと思います。 どういったウェブサイト? オライリーの書籍を販売している書店を、マップや一覧で確認可能なものです。 都道府県別や、現在地点から近い店舗を表示する機能なども備えてます。 旅先でオライリーの書籍が欲しくなるような、オライリー中毒のあなたもこれで大丈夫! (もちろん、レスポンシブデザインなのでスマホでもOK) 全国のオライリー特約店が日本地図にマッピングされているのは壮観です。 情報ソースや構造など 情報のソースとしてはオーム社さんで公開されてる、オライリーの取扱店情報を使用しています。 http://www.ohmsha.co.jp/data/bookstore/bookstore_home.htm この情報には緯度軽度がないので別途、plus codeの情報取得し緯度軽度に変換してマップ状に可視化しています。 https://maps.google.com/pluscodes/ アプリケーションとしては2つあり、 情報をJSonファイルとして出力するNode.jsアプリケーション(ツール) JSonファイルをデータストアとして使用するBlazorアプリケーション といった構造になっています。 Blazor WebAssemblyをGitHub Page上でホストし、デプロイはGitHub Actionsを使用しています。 Node.jsとBlazor用の2つのGitHub Actionを用意し、Node.js側はスケジュールにより定期的に実行されて、情報を自動更新する仕組みになっています。 GitHub Actionsのスケジュール実行に関しては下記が参考になります。 https://qiita.com/Broccolingual/items/bf0fa0d00920fbfda371 Blazor側使用技術 MudBlazor BlazorのUIフレームワークです。 マテリアルデザインで作られていて、CSSやHTMLを直接駆使しなくても、簡単にモダンな見た目のWEBサイトが構築できるようになります。 UIのコンポーネントも豊富で、かなり使い勝手が高い便利なフレームワークです。 今回.NET5から6にアップデートするにあたっては、破壊的な変更が何点かありましたが、簡単に対応することはできました。 (アイコン周りの名称が変わった事やインジェクトするメソッドが追加された等) MudBlazorに関しては、@jsakamotoさんの記事が参考になります。 leaflet Webで地図を表示するためのJavascirptライブラリです。 地図周りの処理はこのライブラリを使用して実装しています。 BlazorはJavascirptの相互運用機能が用意されているので、Javascirptで作られた豊富な資産が利用できるのもメリットですね。 こんな感じで、地図上に表示するデータをleafletに渡してマーカーを表示し、マーカーが選択された時にBlazor側にコールバックを行う、といったような相互作用も簡単にできてしまいます。 blazor @inject IJSRuntime js; <div> <div id="mapcontainer" style="height: @MapHeight; width: 100%; margin:0;"></div> </div> @code { // コールバック [Parameter] public EventCallback<Store> OnStoreMarkerSelected { get; set; } ... public async Task InitAsync(List<Store> stores, Domains.Models.Position position) { // jsの動的ロード var mapModule = await js.InvokeAsync<IJSObjectReference>("import", "./js/leaflet-map.js"); ... // コールバック用に参照を渡す var objRef = DotNetObjectReference.Create(this); await mapModule.InvokeVoidAsync("initMap", objRef); ... // 表示用のデータを渡す await mapModule.InvokeVoidAsync("setData", stores); } // 地図上でマーカーが選択されたら呼ばれるコールバック [JSInvokable] public void OnStoreMarkerClicked(Store store) { OnStoreMarkerSelected.InvokeAsync(store); } } leaflet let map; let dotnet; export function initMap(dotNetHelper, viewPoint) { // コールバック用にblazor側のインスタンスを保持 dotnet = dotNetHelper; ... } let layer; export function setData(storeList) { ... // Blazor側から渡された店舗情報をマーカーとして設定 storeList.forEach((store) => { layer = addMakerWithClickEvent(layer, store); }); map.addLayer(layer); } // 店舗情報をマーカーとして配置 function addMakerWithClickEvent(layer, storeData) { try { const maker = L.marker([storeData.position.latitude, storeData.position.longitude], { draggable: false, }).on('click', function (e) { onMarkerClicked(storeData); }); //マーカークリック時 layer.addLayer(maker); return layer; } catch (err) { console.error("add error", err); } } // blazor側にマップ上で選択された店舗情報をコールバックする function onMarkerClicked(store) { dotnet.invokeMethodAsync('OnStoreMarkerClicked', store); } もともとleafletは使ったことがあり、js側での操作に慣れていたため、試してはいませんが、leafletをBlazor用にラップしたライブラリなどもあるようなのでこちらを使うともっと手軽に実装できるかもしれませんね。 有名なjs系のライブラリだと、Blazor用にラップしたものなどが用意されていて簡単に使えたりします。 GeoCoordinate.NetCore 2点間の緯度、経度から距離を算出することができるライブラリです。 現在地と各店舗の距離を算出して、最寄りの店舗を探すといった事を実現しています。 .NET FrameworkにはSystem.Device.Location.GeoCoordinateクラスで算出できますが、.NET(.NET Core)にはこのクラスがないため、こちらを使用しています。 Blazor向けに作られたものではありませんが、こういった.NET用のライブラリがそのまま使えてしまうのもBlazorの強みです。 GISなどを使った、本格的な地理情報を用いたシステムを作る際にはNetTopologySuiteを使うほうがよさそうですが、距離の精度や細かい設定なども必要ないので、とりあえずお手軽さを優先しました。 まとめ 簡単ですが、Blazorを使用して作成したアプリケーションを紹介しました。 C#で書いたアプリケーションがGitHub PageやFirebase等の静的ページホスティングサービスで手軽にデプロイできるのが、Blazor(WebAssembly)の良さだと思うので、思いつたネタ等はとりあえず勢いで作ってしまったりすると、面白いのではないかと思います。 豆知識 ご存知な方も多いと思いますが、オライリー本の表紙で使用されている動物は、下記のイラスト集から使用されているものが多いです。 この書籍の最初に書いてありますが、所持者は最大4つまで1つの作品でイラストを使用する事ができます。 (それ以上は連絡と許諾が必要な模様) kindleで電子書籍版も購入可能で、入手容易なのも嬉しいポイントです。 オライリー好きならば1冊は持っておきたいですね!! 実は今回紹介したページも、ランダムで4種類の動物の画像が出るようになっています。
- 投稿日:2021-12-24T00:07:39+09:00
アダルトサイト開発はカーテンの向こう側
この記事は 驚異のFANZA女優検索 Advent Calendar 2021 の 24日目の記事です。 アダルトサイトを作ったとてすぐに人が溢れるかというとそんなことは全然ない アダルトサイトを作ってみて思ったことは普通のサイト制作よりも結構大変なことがある。正直アダルトサイトを作れば、すぐに人がわんさか来て、収益もバンバン上がってという想像をしていたが、サイトの収益についてに書いたとおり全然そんなことはない。 アダルトサイトを運営すると実は使えないものが結構多い 似た顔のFANZA女優を顔写真から検索出来るサンプルを用意するのに苦労したにも書いたが、アダルトサイトは思っていた以上に制限が多い。例えばフリー素材の画像を使おうと思っても、サイトの規約を読むとアダルトサイトはNGと書いてあるケースは本当に多い。フリーを含めた素材が一切使えないのはかなり厳しい制限だと思う。そういえば、サーバーもアダルトコンテンツを置く時点でNGな所もあるし、なかなかに厳しい。 アダルトサイト開発は自宅ではやりにくい 自宅で特に家庭を持っているとアダルトサイトの開発は難しい。特に隠しているわけでもないが、わざわざ言う事でもないと思って作っている宣言はしていない。そうなると、パソコンの前から離れる時や、後ろに回られそうになった時は画面が見られないようにしている。そのためもあってFirefoxを開発専用のブラウザにしているので、Alt+Tabでchromeを先頭に持ってくるスキルはガンマン並みに速くなっていると思う。 アダルトサイト開発は後ろめたさがある そもそも、やはりアダルトサイトを作っていると、特に悪いことしていなくても、ある日このようなサイトは止めてくださいという注意が何処かから来たり、突然Twitterのアカウントが凍結されるんじゃないかと思ったりする。さらに心のどこかでそういう理不尽な事があっても、「え、そんな馬鹿な!」とは思えず「あ、やっぱりな」と思ってしまう。そういうのもあって2つの名前の管理の仕方で書いたがアカウントを分けた。普通に開発をしているだけではこういう思考にはならないと思うので、こういう後ろめたさを感じてしまっている事こそが、アダルトサイト開発のもっとも大きな制限かもしれない。 アダルトサイト開発はカーテンの向こう側 そもそも自分の性的思考なんてものは、本来日の当たる場所に晒すことではないと思うし、それを前面に押し出すのは下品な事だと思う。上品なアダルトサイトなんて、清純派セクシー女優ぐらい矛盾に満ちた表現な気もするが、つまるところ自分はダサいのが嫌なのだ。アダルトサイトの相互リンクに自分のサイトも乗っかろうとして止めたのもほぼ同じ理由だ。別の言い方をするとアダルトサイト開発はカーテンの向こう側なんだと思う。この表現、もう令和では通じなくなるだろうなー。