正規表現の構文設計の可読性が低い特定の理由はありますか?


160

プログラマは皆、コードの可読性が機能する短い構文のワンライナーよりもはるかに重要であることに同意しているように見えますが、上級開発者がある程度の精度で解釈する必要があります-それはまさに正規表現が設計された方法のようです。これには理由がありましたか?

私たちは皆、それselfDocumentingMethodName()がはるかに良いことに同意しe()ます。なぜ正規表現にも当てはまらないのですか?

構造的な編成のない1行のロジックの構文を設計するのではなく、

var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})(0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

そして、これはURLの厳密な解析でさえありません!

代わりに、基本的な例として、パイプライン構造を整理して読みやすくすることができます。

string.regex
   .isRange('A-Z' || 'a-z')
   .followedBy('/r');

正規表現の非常に簡潔な構文は、最短の操作と論理構文以外にどのような利点がありますか?最終的に、正規表現の構文設計の可読性が低い特定の技術的な理由はありますか?


コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
maple_shaft

1
RegexToolboxと呼ばれるライブラリを使用して、この読みやすさの問題に正確に取り組んでみました。これまでのところ、C#、Java、およびJavaScriptに移植されています-github.com/markwhitaker/RegexToolbox.CSharpを参照してください。
マーク・ウィテカー

この問題を解決するために多くの試みが行われましたが、文化を変えることは困難です。ここで言葉の表現に関する私の答えを見てください。人々は利用可能な最低の一般的なツールに手を伸ばします。
パリバールサラフ

回答:


178

彼らがそうであるように正規表現を簡潔として設計された理由の一つの大きな理由があります:彼らはないのコードへの言語として、コードエディタにコマンドとして使用するように設計されたより正確には、。ed正規表現を使用した最初のプログラムの一つでした、そしてそこから正規表現が世界支配の征服を始めました。たとえば、このedコマンドはg/<regular expression>/pすぐに別のプログラムに影響を与え、grep現在も使用されています。そのため彼らのパワーを、彼らはその後、標準化されたなどの様々なツールで使用されるsedと、vim

しかし、トリビアには十分です。では、なぜこの起源が簡潔な文法を好むのでしょうか?もう一度それを読むためにエディタコマンドを入力しないからです。組み立て方を覚えていれば十分で、やりたいことはそれで十分です。ただし、入力する必要があるすべての文字は、ファイルの編集の進行を遅くします。正規表現の構文は、比較的複雑な検索をスローアウェイ方式で記述するように設計されており、それがまさに、プログラムへの入力を解析するためのコードとして使用する人々の頭痛の種です。


5
正規表現は解析するためのものではありません。それ以外の場合、stackoverflow.com / questions / 1732348 /…。と頭痛。
njzk2

19
@ njzk2その答えは実際には間違っています。HTML ドキュメントは通常の言語ではなく、HTMLのオープンタグであり、これが実際に質問されている質問です。
Random832

11
これは、元の正規表現がそのまま不可解である理由を説明する良い答えですが、現在、読みやすさを高めた代替標準がない理由を説明していません。
Doc Brown

13
grep誤発音された「グラブ」であると考えている人にとっては、実際にはg/ re(正規表現の場合)/ p
ハーゲンフォンアイゼン

6
@DannyPflughoeftいいえ、ありません。開始タグはただ<aaa bbb="ccc" ddd='eee'>、タグはネストされていません。タグをネストすることはできません。ネストするのは要素(開始タグ、子要素を含むコンテンツ、終了タグ)であり、解析で質問されていません。HTML タグは通常の言語です。バランス/ネストはタグの上のレベルで発生します。
Random832

62

あなたが引用する正規表現はひどい混乱であり、誰もがそれが読みやすいことに同意するとは思わない。同時に、そのugさの多くは解決される問題に固有のものです。ネストのいくつかの層があり、URL文法は比較的複雑です(どの言語でも簡潔に通信するには確かに複雑すぎます)。ただし、この正規表現が何を記述しているかを記述するより良い方法があることは確かに真実です。では、なぜ使用されないのですか?

大きな理由は、慣性と遍在です。そもそも彼らがどのように人気を博したのかは説明していませんが、今では正規表現を知っている人なら誰でもこれらのスキル(方言の違いはほとんどありません)を100の異なる言語と追加の1,000のソフトウェアツール(たとえば、テキストエディターやコマンドラインツール)。ちなみに、後者は、非プログラマーによって頻繁に使用されるため、プログラムの作成に相当するソリューションを使用することも、使用することもできません。

それにもかかわらず、正規表現は頻繁に使いすぎられます。つまり、別のツールがはるかに優れている場合でも適用されます。正規表現の構文はひどいとは思いません。しかし、短くて単純なパターンでは明らかにはるかに優れています:Cのような言語の識別子の典型的な例は[a-zA-Z_][a-zA-Z0-9_]*、絶対的な最小限の正規表現知識で読むことができ、そのバーが満たされると、明白で簡潔になります。必要な文字数を減らすことは本質的に悪いことではなく、まったく逆です。簡潔であることは、あなたが理解しやすい限り、美徳で​​す。

この構文がこれらのような単純なパターンに優れているのには、少なくとも2つの理由があります。ほとんどの文字をエスケープする必要がないため、比較的自然に読み取れます。また、使用可能なすべての句読点を使用して、さまざまな単純な解析コンビネーターを表現します。おそらく最も重要なことは、シーケンスのために何も必要としないことです。あなたは最初のものを書き、それから来るものを書きます。あなたとは対照的followedBy、次のパターンがある場合は特に、ないリテラルが、より複雑な式。

それでは、なぜより複雑なケースでは不十分なのでしょうか?私は3つの主な問題を見ることができます:

  1. 抽象化機能はありません。正規の文法は、正規表現と同じ理論的コンピューターサイエンスの分野に由来するものであり、一連の生成があるため、パターンの中間部分に名前を付けることができます。

    # This is not equivalent to the regex in the question
    # It's just a mock-up of what a grammar could look like
    url      ::= protocol? '/'? '/'? '/'? (domain_part '.')+ tld
    protocol ::= letter+ ':'
    ...
    
  2. 上記でわかるように、特別な意味を持たない空白は、目に優しいフォーマットを許可するのに役立ちます。コメントも同じです。正規表現では、スペースはまさにリテラルであるため、これを行うことはできません' '。ただし、一部の実装では、空白が無視されコメントが可能な「冗長」モードが許可されています。

  3. 一般的なパターンとコンビネータを記述するメタ言語はありません。例えば、一つは書くことができdigit、一度ルールをし、文脈自由文法でそれを使い続けますが、一つはそれをいわば「機能」を定義することはできません生産が与えられp、それに余分な何かをする新たな生産を作成し、例えば作成のオカレンスのコンマ区切りリストのプロダクションp

あなたが提案するアプローチは確かにこれらの問題を解決します。必要以上に簡潔に取引されているため、あまりうまく解決できません。最初の2つの問題は、比較的単純で簡潔なドメイン固有の言語のままで解決できます。3番目、まあ...プログラムによる解決策には、もちろん汎用プログラミング言語が必要ですが、私の経験では、3番目はこれらの問題の中で最も少ないです。プログラマが新しいコンビネータを定義することを切望しているのと同じ複雑なタスクが十分に発生するパターンはほとんどありません。そして、これが必要な場合、言語はしばしば複雑で、とにかく正規表現で解析することはできませんし、すべきではありません。

これらの場合のソリューションが存在します。およそ1万のパーサーコンビネーターライブラリがあり、おおよその提案を実行します。異なる操作セット、多くの場合異なる構文を使用し、ほぼ常に正規表現よりも解析力が高い(つまり、コンテキストのない言語またはかなりのサイズを処理します)それらのサブセット)。次に、前述の「より良いDSLを使用する」アプローチを採用したパーサージェネレーターがあります。そして、適切なコードで、解析の一部を手で書くオプションが常にあります。単純なサブタスクに正規表現を使用し、正規表現を呼び出すコードで複雑なことを行うこともできます。

コンピューティングの初期の頃について、正規表現がどのように普及したのかを説明するのに十分な知識がありません。しかし、彼らはここにいます。あなたはそれらを賢く使用する必要があり、それが賢明なときにそれらを使用しないでください。


9
I don't know enough about the early years of computing to explain how regular expressions came to be so popular.ただし、推測を危険にさらす可能性があります。基本的な正規表現エンジンは実装が非常に簡単で、効率的なコンテキストフリーパーサーよりもはるかに簡単です。
biziclop

15
@biziclopこの変数を過大評価しないでください。「さらに別のコンパイラコンパイラ」と呼ばれる十分な前任者があるように見えるYaccは、70年代前半に作成され、以前のバージョンgrep(バージョン3とバージョン4)がUnixに含まれていました。正規表現の最初の主要な使用は1968

私はウィキペディアで見つけたものだけに行くことができます(したがって100%信じていません)が、それによればyacc、1975年に作成された、LALRパーサーの完全なアイデアJITが式をコンパイルした最初の正規表現エンジンの実装(!)が1968年に公開されたのに対して、それは揺れたものを言うのは難しいです。実際、正規表現が「オフ"。しかし、開発者が使用するテキストエディタに配置されたら、自分のソフトウェアでも使用したかったのではないかと思います。
-biziclop

1
@ jpmc26は、彼の本、JavaScript The Good Parts to the Regex Chapterを開きます。
15

2
with very few differences between dialects「ごく少数」とは言いません。定義済みの文字クラスには、さまざまな方言間でいくつかの定義があります。また、各方言に固有の構文解析の癖もあります。
nhahtdh

39

歴史的視点

ウィキペディアの記事は、正規表現の起源について非常に詳細です(Kleene、1956)。オリジナルの構文は比較的簡単だった*+?|およびグループ化(...)。それは簡潔だった(正式言語は簡潔な数学的表記で表現される傾向があるので、読みやすい、二つは必ずしも反対ではありません)。

後に、構文と機能はエディターで進化し、Perlで成長しました。Perlは設計によって簡潔にしようとしていました(「共通の構造は短くすべきです」)。これにより構文がかなり複雑になりましたが、今では人々は正規表現に慣れており、それらを書くこと(読まない場合)が得意であることに注意してください。書き込み専用である場合があるという事実は、長すぎる場合、通常は適切なツールではないことを示唆しています。 正規表現は、悪用されると判読できなくなる傾向があります。

文字列ベースの正規表現を超えて

代替構文についていえば、のは(すでに存在するものを見てみましょうCL-ppcreで、Common Lispのを)。長い正規表現はppcre:parse-string、次のように解析できます。

(let ((*print-case* :downcase)
      (*print-right-margin* 50))
  (pprint
   (ppcre:parse-string "^(?:([A-Za-z]+):)?(\\/{0,3})(0-9.\\-A-Za-z]+)(?::(\\d+))?(?:\\/([^?#]*))?(?:\\?([^#]*))?(?:#(.*))?$")))

...そして次の形式になります:

(:sequence :start-anchor
 (:greedy-repetition 0 1
  (:group
   (:sequence
    (:register
     (:greedy-repetition 1 nil
      (:char-class (:range #\A #\Z)
       (:range #\a #\z))))
    #\:)))
 (:register (:greedy-repetition 0 3 #\/))
 (:register
  (:sequence "0-9" :everything "-A-Za-z"
   (:greedy-repetition 1 nil #\])))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\:
    (:register
     (:greedy-repetition 1 nil :digit-class)))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\/
    (:register
     (:greedy-repetition 0 nil
      (:inverted-char-class #\? #\#))))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\?
    (:register
     (:greedy-repetition 0 nil
      (:inverted-char-class #\#))))))
 (:greedy-repetition 0 1
  (:group
   (:sequence #\#
    (:register
     (:greedy-repetition 0 nil :everything)))))
 :end-anchor)

この構文はより冗長であり、以下のコメントを見ると、必ずしも読みやすいとは限りません。そのため、構文がコンパクトではないため、物事が自動的に明確になると想定しないでください

ただし、正規表現で問題が発生し始めた場合は、正規表現をこの形式に変換すると、コードの解読とデバッグに役立つ場合があります。これは、1文字のエラーを見つけるのが難しい場合がある文字列ベースのフォーマットに対する利点の1つです。 この構文の主な利点は、文字列ベースのエンコードではなく、構造化された形式を使用して正規表現を操作することです。これにより、プログラム内の他のデータ構造のような式を作成および構築できます。上記の構文を使用する場合、これは一般に、小さな部分から式を作成するためです(CodeGolfの回答も参照してください)。あなたの例では、1を書くかもしれません:

`(:sequence
   :start-anchor
   ,(protocol)
   ,(slashes)
   ,(domain)
   ,(top-level-domain) ... )

文字列ベースの正規表現は、文字列の連結および/またはヘルパー関数でラップされた補間を使用して構成することもできます。ただし、コード乱雑にする傾向のある文字列操作には制限があります(バックスラッシュ$(...)とバッシュの違いとは異なり、ネストの問題について考えてください。また、エスケープ文字は頭痛の種になります)。

また、上記のフォームではフォームを使用できる(:regex "string")ため、簡潔な表記とツリーを混在させることができます。そのすべてが、私見を優れた読みやすさと構成可能性に導きます。delnanによって間接的に表現された3つの問題に対処します(つまり、正規表現自体の言語ではありません)。

結論する

  • ほとんどの場合、簡潔な表記は実際に読み取り可能です。バックトラッキングなどを含む拡張表記を扱う場合は困難がありますが、それらの使用が正当化されることはめったにありません。正規表現を不当に使用すると、表現が読めなくなる可能性があります。

  • 正規表現は文字列としてエンコードする必要はありません。正規表現の作成と作成に役立つライブラリまたはツールがあれば、文字列操作に関連する多くの潜在的なバグを回避できます。

  • 代わりに、形式文法は読みやすく、部分式の命名と抽象化が優れています。通常、端末は単純な正規表現として表されます。


1.正規表現はアプリケーションの定数になる傾向があるため、読み取り時に式を作成することをお勧めします。参照してくださいcreate-scannerload-time-value

'(:sequence :start-anchor #.(protocol) #.(slashes) ... )

5
たぶん、私は伝統的な正規表現構文に慣れているだけかもしれませんが、22の多少読みやすい行が、同等の1行の正規表現よりも理解しやすいかどうかはわかりません。

3
@あなたが本当に長い正規表現を持っている必要がある場合さて、しかし、それはのように、サブセットを定義するために理にかなっています;-) dan1111「やや読みやすい」digitsidentおよびそれらを構成します。私が見た彼らのやり方は、一般に文字列操作(連結または補間)であり、適切なエスケープのような他の問題をもたらします。\\\\`たとえば、emacsパッケージ内の出現を検索します。ところで、これはさらに悪化します。なぜなら、\n\"などの特殊文字の両方に同じエスケープ文字が使用されているため\(です。良い構文の非lispの例があるprintfところ、%dと矛盾しません\d
コアダンプ

1
定義されたサブセットに関する公平なポイント。それは理にかなっています。私は、冗長性が改善であることに懐疑的です。初心者にとっては簡単かもしれません(ただし、概念greedy-repetitionは直感的ではなく、まだ学習する必要があります)。ただし、パターン全体を確認して把握することははるかに難しいため、エキスパートの使いやすさは犠牲になります。

@ dan1111冗長性自体は改善されないことに同意します。改善できるのは、文字列ではなく構造化データを使用して正規表現を操作することです。
コアダンプ

@ dan1111 Haskellを使用して編集を提案すべきでしょうか?Parsecはたった9行でそれを行います。ワンライナーとして:do {optional (many1 (letter) >> char ':'); choice (map string ["///","//","/",""]); many1 (oneOf "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."); optional (char ':' >> many1 digit); optional (char '/' >> many (noneOf "?#")); optional (char '?' >> many (noneOf "#")); optional (char '#' >> many (noneOf "\n")); eof}。長い文字列を指定するなどの数行とdomainChars = ...し、section start p = optional (char start >> many p)それは非常に簡単になります。
CR Drost

25

正規表現の最大の問題は、過度に簡潔な構文ではなく、小さなビルディングブロックから構成するのではなく、単一の式で複雑な定義を表現しようとすることです。これは、変数や関数を使用せずに、コードをすべて1行に埋め込むプログラミングに似ています。

正規表現とBNFを比較します。その構文は正規表現ほどきれいではありませんが、使用方法が異なります。まず、単純な名前付きシンボルを定義し、一致させるパターン全体を記述するシンボルに到達するまでそれらを構成します。

たとえば、rfc3986のURI構文を見てください

URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
hier-part     = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty
...

名前付き部分式の埋め込みをサポートする正規表現構文のバリアントを使用して、ほぼ同じことを書くことができます。


個人的には、構文のような簡潔な正規表現は、文字クラス、連結、選択、繰り返しなどの一般的に使用される機能には適していますが、先読み冗長名などのより複雑でまれな機能には適していると思います。通常のプログラミングで、+または*通常のプログラミングで演算子を使用し、よりまれな操作のために名前付き関数に切り替える方法とかなり似ています。


12

selfDocumentingMethodName()はe()よりもはるかに優れています

それは...ですか?ほとんどの言語で、BEGINとENDではなく{と}がブロック区切り文字として使用されているのには理由があります。

人々は簡潔さを好み、構文がわかれば、短い用語のほうが優れています。d(数字用)が「数字」であった場合の正規表現の例を想像してください。正規表現はさらに読みにくいものになります。制御文字を使用してより簡単に解析できるようにすると、XMLのようになります。構文がわかれば、どちらも優れていません。

あなたの質問に適切に答えるためには、正規表現は簡潔さが必須であった時代から来ていることを認識しなければなりません。1MBのXML文書は今日では大したことではないと考えるのは簡単ですが、1 MBがほぼストレージ容量全体。当時使用されていた言語も少なく、正規表現はperlやCから100万マイルも離れていません。そのため、構文は、構文の学習に満足している当時のプログラマーになじみがあります。したがって、より冗長にする理由はありませんでした。


1
selfDocumentingMethodNameされ、一般的に合意されたよりも優れていることがe、プログラマの直感と整列していないため、実際に読みやすさや質の良いコードを構成するものの面で現実。同意する人々は間違っていますが、それはそうです。
ルーシェンコ

1
@Leushenko:それe()はそれよりも優れていると主張していますselfDocumentingMethodName()か?
ジャックB

3
@JacquesBは、すべてのコンテキスト(グローバル名など)にあるとは限りません。しかし、厳密にスコープされたものについてはどうでしょうか?ほぼ間違いなく。従来の知恵が言うよりも間違いなく頻繁に。
ルーシェンコ

1
@Leushenko:コンテキストがよりわかりやすい名前よりも1文字の関数名であると想像するのは大変です。しかし、これは純粋な意見だと思います。
ジャックB

1
@MilesRout:この例は、実際にe()は自己文書化メソッド名に対するものです。メソッド名を説明するのではなく、1文字のメソッド名を使用する方が改善されるコンテキストを説明できますか?
ジャックB

6

正規表現はレゴのピースのようなものです。一見すると、結合できるさまざまな形状のプラスチック部品があります。あなたはあなたが形作ることができるあまりにも多くの異なるものはないと思うかもしれませんが、その後、あなたは他の人が行う驚くべきことを見て、あなたはそれがどれだけすばらしいおもちゃであるかと思います。

正規表現はレゴのピースのようなものです。使用できる引数はほとんどありませんが、さまざまな形式で連鎖させると、多くの複雑なタスクに使用できる数百万の異なる正規表現パターンが形成されます。

人々はめったに正規表現パラメータを使用しませんでした。多くの言語では、文字列の長さをチェックしたり、数値部分を分割したりする機能が提供されています。文字列関数を使用して、テキストをスライスして再構成できます。複雑なフォームを使用して非常に特定の複雑なタスクを実行すると、正規表現の力に気付きます。

SOでは何万もの正規表現の質問を見つけることができ、重複としてマークされることはほとんどありません。これだけでも、互いに非常に異なる可能性のあるユニークなユースケースを示しています。

そして、この非常に異なるユニークなタスクを処理するための事前定義されたメソッドを提供することは簡単ではありません。これらの種類のタスク用の文字列関数がありますが、それらの関数が特定のタスクに十分でない場合は、正規表現を使用する時間です


2

私はこれが効力ではなく実践の問題であることを認識しています。この問題は通常、複合的な性質を仮定するのではなく、正規表現が直接実装されたときに発生します。同様に、優れたプログラマーは、プログラムの機能を簡潔なメソッドに分解します。

たとえば、URLの正規表現文字列は次のように削減できます。

UriRe = [scheme][hier-part][query][fragment]

に:

UriRe = UriSchemeRe + UriHierRe + "(/?|/" + UriQueryRe + UriFragRe + ")"
UriSchemeRe = [scheme]
UriHierRe = [hier-part]
UriQueryRe = [query]
UriFragRe = [fragment]

正規表現は気の利いたものですが、見かけの複雑さに夢中になってしまう人たちによって乱用されがちです。結果の表現はレトリックであり、長期的な価値はありません。


2
残念ながら、ほとんどのプログラミング言語には正規表現の作成に役立つ機能が含まれておらず、グループキャプチャの動作方法も作成にあまり適していません。
CodesInChaos

1
他の言語では、「perl compatible regular expression」サポートでPerl 5に追いつく必要があります。部分式は、単に正規表現仕様の文字列を連結することと同じではありません。暗黙的な番号付けに依存せずに、キャプチャに名前を付ける必要があります。
JDługosz

0

@cmasterが言うように、正規表現は元々オンザフライでのみ使用するように設計されており、ラインノイズ構文が依然として最も人気のあるものであるのは単に奇妙(そして少し気のめいる)です。私が考えることができる唯一の説明は、慣性、マゾヒズム、またはマチスモのいずれかに関するものです(「慣性」が何かをするための最も魅力的な理由であるということはあまりありません...)

Perlは、空白とコメントを許可することで、それらをより読みやすくするためのかなり弱い試みを行いますが、リモートで想像力を働かせることはしません。

他の構文があります。良いものはregexpsscsh構文です。私の経験では、かなり簡単に入力できますが、事実を読み取った後でも読みやすい正規表現を生成します。

[ scshは他の理由で素晴らしい。その一つは有名な謝辞のテキストである ]


2
Perl6はそうです!文法を見てください。
JDługosz

@JDługosz私が見る限り、これは正規表現の代替構文というよりも、パーサージェネレーターのメカニズムに似ています。しかし、区別は深いものではないかもしれません。
ノーマングレイ

交換することもできますが、同じ電力に限定されません。regedpを修飾子が1対1で対応するインライン文法に変換できますが、構文はより読みやすくなります。このように宣伝する例は、元のPerl Apocalypseにあります。
JDługosz

0

正規表現は、可能な限り「一般的」でシンプルになるように設計されているため、どこでも(ほぼ)同じように使用できます。

あなたの例はregex.isRange(..).followedBy(..)、特定のプログラミング言語の構文と、おそらくオブジェクト指向スタイル(メソッドチェーン)の両方に結合されています。

たとえば、この正確な「正規表現」はCではどのように見えますか?コードを変更する必要があります。

最も「一般的な」アプローチは、単純な簡潔な言語を定義し、変更せずに他の言語に簡単に組み込むことができるようにすることです。そして、それが(ほぼ)正規表現です。


0

Perl互換の正規表現エンジンは広く使用されており、多くのエディターや言語が理解できる簡潔な正規表現構文を提供します。@JDługoszがコメントで指摘したように、Perl 6(Perl 5の新しいバージョンではなく、まったく異なる言語)は、個別に定義された要素から正規表現を構築することで、より読みやすくしようとしました。たとえば、Wikibooksの URLを解析するための文法の例を次に示します。

grammar URL {
  rule TOP {
    <protocol>'://'<address>
  }
  token protocol {
    'http'|'https'|'ftp'|'file'
  }
  rule address {
    <subdomain>'.'<domain>'.'<tld>
  }
  ...
}

このように正規表現を分割すると、各ビットを個別に定義する(たとえばdomain、英数字に制限する)か、サブクラス化によって拡張する(たとえばFileURL is URL、その制限protocolのみにする"file")ことができます。

だから、いいえ、正規表現の簡潔さの技術的な理由はありませんが、それらを表現するためのより新しく、よりクリーンで読みやすい方法がすでにここにあります!したがって、この分野で新しいアイデアが見つかることを期待しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.