チヌム開発の基瀎ずしおの䞍倉性ず信頌の幻想

䞀般的に私はC ++プログラマです。 たあそれは起こった。 私のキャリアで曞いた商甚コヌドの倧郚分はC ++です。 私は、ある蚀語に察する個人的な経隓のこのような匷い偏りがあたり奜きではありたせん。別の蚀語で䜕かを曞く機䌚を逃さないようにしおいたす。 そしお、私の珟圚の雇甚䞻は突然そのような機䌚を提䟛したした。私は、Javaで最も些现なナヌティリティではないものを䜜るこずを玄束したした。 実装蚀語の遞択は歎史的な理由で行われたしたが、私は気にしたせんでした。 JavaだからJava、私にはあたり銎染みがない-良い。

ずりわけ、論理的に関連する特定のデヌタのセットを䜜成しお特定のコンシュヌマヌに転送するずいう、かなり単玔なタスクがありたした。 いく぀かのコンシュヌマヌが存圚する可胜性があり、カプセル化の原則によれば、送信コヌドプロデュヌサヌは、゜ヌスデヌタで䜕ができ、゜ヌスデヌタで䜕ができるかを知りたせん。 しかし、補造業者は各消費者が同じデヌタを受け取る必芁がありたす。 コピヌを䜜っおあげたくありたせんでした。 これは、消費者に送信されるデヌタを倉曎する機䌚を䜕らかの方法で奪う必芁があるこずを意味したす。

その時から、Javaでの私の䞍慣れさが感じられたした。 C ++ず比范しお蚀語機胜が欠けおいたした。 はい、 finalキヌワヌドがありたすが、 final final Objectはconst Object*ではなく、C ++のObject* constに䌌おいたす。 ぀たり final List<String>には、たずえば行を远加できたす。 それはC ++ビゞネスです。マむダヌズの蚌蚀に埓っおconstどこにでも配眮するこず、それだけです 誰も䜕も倉えたせん。 だから たあ、そうでもない。 私は䜙暇にそのナヌティリティを実行する代わりに、これに぀いお少し考えたした。それが私がやったこずです。

C ++


タスク自䜓を思い出させおください

  1. デヌタセットを1回䜜成したす。
  2. 䞍必芁にコピヌしないでください。
  3. 消費者がこのデヌタを倉曎できないようにしたす。
  4. コヌドを最小化、぀たり 䞀般に、ほんの数か所で、必芁なデヌタセットごずに倚数のメ゜ッドずむンタヌフェむスを䜜成しないでください。

マルチスレッド、䟋倖的な意味でのセキュリティなどの悪化する条件はありたせん。 最も単玔なケヌスを考えおみたしょう。 私が最も䜿い慣れた蚀語を䜿甚しおそれを行う方法は次のずおりです。

foo.hpp
 #pragma once #include <iostream> #include <list> struct Foo { const int intValue; const std::string strValue; const std::list<int> listValue; Foo(int intValue_, const std::string& strValue_, const std::list<int>& listValue_) : intValue(intValue_) , strValue(strValue_) , listValue(listValue_) {} }; std::ostream& operator<<(std::ostream& out, const Foo& foo) { out << "INT: " << foo.intValue << "\n"; out << "STRING: " << foo.strValue << "\n"; out << "LIST: ["; for (auto it = foo.listValue.cbegin(); it != foo.listValue.cend(); ++it) { out << (it == foo.listValue.cbegin() ? "" : ", ") << *it; } out << "]\n"; return out; } 


api.hpp
 #pragma once #include "foo.hpp" #include <iostream> class Api { public: const Foo& getFoo() const { return currentFoo; } private: const Foo currentFoo = Foo{42, "Fish", {0, 1, 2, 3}}; }; 

main.cpp
 #include "api.hpp" #include "foo.hpp" #include <list> namespace { void goodConsumer(const Foo& foo) { // do nothing wrong with foo } } int main() { { const auto& api = Api(); goodConsumer(api.getFoo()); std::cout << "*** After good consumer ***\n"; std::cout << api.getFoo() << std::endl; } } 


明らかに、ここではすべおが正垞であり、デヌタは倉曎されおいたせん。

おわりに
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0, 1, 2, 3] 

そしお誰かが䜕かを倉えようずしたら


main.cpp
 void stupidConsumer(const Foo& foo) { foo.listValue.push_back(100); } 


はい、コヌドはコンパむルされたせん。

゚ラヌ
 src/main.cpp: In function 'void {anonymous}::stupidConsumer(const Foo&)': src/main.cpp:16:36: error: passing 'const std::__cxx11::list<int>' as 'this' argument discards qualifiers [-fpermissive] foo.listValue.push_back(100); 


䜕がおかしいのでしょうか


これはC ++です-自分の足で撮圱するための豊富な歊噚を備えた蚀語です 䟋

main.cpp
 void evilConsumer(const Foo& foo) { const_cast<int&>(foo.intValue) = 7; const_cast<std::string&>(foo.strValue) = "James Bond"; } 


たあ、実際にはすべお
 *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


たた、この堎合にconst_cast代わりにconst_castを䜿甚するず、コンパむル゚ラヌが発生するこずに泚意しおください。 ただし、Cスタむルのキャストを䜿甚するず、この焊点を絞るこずができたす。

はい、そのようなコヌドは未定矩の動䜜[C ++ 17 10.1.7.1/4]に぀ながる可胜性がありたす。 圌は䞀般的に疑わしいように芋えたすが、それは良いこずです。 レビュヌ䞭にキャッチしやすい。

悪意のあるコヌドがナヌザヌの奜みの深さたで隠れるのは悪いこずですが、それでも動䜜したす

main.cpp
 void evilSubConsumer(const std::string& value) { const_cast<std::string&>(value) = "Loki"; } void goodSubConsumer(const std::string& value) { evilSubConsumer(value); } void evilCautiousConsumer(const Foo& foo) { const auto& strValue = foo.strValue; goodSubConsumer(strValue); } 


おわりに
 *** After evil but cautious consumer *** INT: 42 STRING: Loki LIST: [0, 1, 2, 3] 


このコンテキストでのC ++の長所ず短所


どちらが良いですか

悪い点


Java


Javaでは、私が理解しおいるように、わずかに異なるアプロヌチが䜿甚されたす。 finalずしお宣蚀されたプリミティブ型は、C ++ず同じ意味で定数です。 Javaのfinal Stringは基本的に䞍倉であるため、この堎合に必芁なのはfinal String的なfinal Stringです。

コレクションは䞍倉のラッパヌに配眮できたす。そのためには、 java.util.Collectionsクラスの静的メ゜ッドunmodifiableList 、 unmodifiableMapなどがありたす。 ぀たり 定数オブゞェクトず非定数オブゞェクトのむンタヌフェヌスは同じですが、非定数オブゞェクトを倉曎しようずするず䟋倖がスロヌされたす。

ナヌザヌタむプに関しおは、ナヌザヌ自身が䞍倉のラッパヌを䜜成する必芁がありたす。 䞀般に、Javaのオプションは次のずおりです。

Foo.java
 package foo; import java.util.Collections; import java.util.List; public final class Foo { public final int intValue; public final String strValue; public final List<Integer> listValue; public Foo(final int intValue, final String strValue, final List<Integer> listValue) { this.intValue = intValue; this.strValue = strValue; this.listValue = Collections.unmodifiableList(listValue); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("INT: ").append(intValue).append("\n") .append("STRING: ").append(strValue).append("\n") .append("LIST: ").append(listValue.toString()); return sb.toString(); } } 


Api.java
 package api; import foo.Foo; import java.util.Arrays; public final class Api { private final Foo foo = new Foo(42, "Fish", Arrays.asList(0, 1, 2, 3)); public final Foo getFoo() { return foo; } } 


Main.java
 import api.Api; import foo.Foo; public final class Main { private static void goodConsumer(final Foo foo) { // do nothing wrong with foo } public static void main(String[] args) throws Exception { { final Api api = new Api(); goodConsumer(api.getFoo()); System.out.println("*** After good consumer ***"); System.out.println(api.getFoo()); System.out.println(); } } } 


おわりに
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0, 1, 2, 3] 


倱敗した倉曎の詊み


たずえば、䜕かを倉曎しようずする堎合

Main.java
 private static void stupidConsumer(final Foo foo) { foo.listValue.add(100); } 


このコヌドはコンパむルされたすが、実行時に䟋倖がスロヌされたす。

䟋倖
 Exception in thread "main" java.lang.UnsupportedOperationException at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1056) at Main.stupidConsumer(Main.java:15) at Main.main(Main.java:70) 


成功した詊み


そしお、悪い方法で 型からfinal修食子を削陀する方法はありたせん。 しかし、Javaにはもっず匷力なものがありたす-リフレクションです。

Main.java
 import java.lang.reflect.Field; private static void evilConsumer(final Foo foo) throws Exception { final Field intField = Foo.class.getDeclaredField("intValue"); intField.setAccessible(true); intField.set(foo, 7); final Field strField = Foo.class.getDeclaredField("strValue"); strField.setAccessible(true); strField.set(foo, "James Bond"); } 


そしお免疫に察する
 *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


このようなコヌドは、C ++のcosnt_castよりもcosnt_cast芋えるため、レビュヌをキャッチするのがさらに簡単です。 そしお、それはたた予枬䞍可胜な効果に぀ながる可胜性がありたす ぀たり、JavaにはUBがありたすか。 たた、任意に深く隠すこずもできたす。

これらの予枬䞍可胜な圱響は、リフレクションを䜿甚しおfinalオブゞェクトが倉曎されたずきに、 hashCode()メ゜ッドによっお返される倀が同じたたである可​​胜性があるためです。 同じハッシュを持぀異なるオブゞェクトは問題になりたせんが、異なるハッシュを持぀同じオブゞェクトは悪いです。

このようなJavaでの文字列専甚のハッキングの危険性は䜕ですか 䟋 。ここでの文字列はプヌルに保存でき、互いに無関係で、同じ文字列だけがプヌル内の同じ倀を瀺すこずができたす。 倉曎されたもの-それらすべおを倉曎したした。

しかし JVMはさたざたなセキュリティ蚭定で実行できたす。 すでにデフォルトのSecurity Managerがアクティブになっおいるため、䞊蚘のすべおのトリックをリフレクションで抑制したす。

䟋倖
 $ java -Djava.security.manager -jar bin/main.jar Exception in thread "main" java.security.AccessControlException: access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks") at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) at java.base/java.security.AccessController.checkPermission(AccessController.java:895) at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:335) at java.base/java.lang.reflect.AccessibleObject.checkPermission(AccessibleObject.java:85) at java.base/java.lang.reflect.Field.setAccessible(Field.java:169) at Main.evilConsumer(Main.java:20) at Main.main(Main.java:71) 


このコンテキストでのJavaの長所ず短所


どちらが良いですか

悪い点


Python


たあ、その埌、私は単に奜奇心の波に流されたした。 このようなタスクは、たずえばPythonでどのように解決されたすか そしお、圌らはたったく決定されおいたすか 実際、Pythonには、そのようなキヌワヌドがなくおも、原則ずしお䞍倉はありたせん。

foo.py
 class Foo(): def __init__(self, int_value, str_value, list_value): self.int_value = int_value self.str_value = str_value self.list_value = list_value def __str__(self): return 'INT: ' + str(self.int_value) + '\n' + \ 'STRING: ' + self.str_value + '\n' + \ 'LIST: ' + str(self.list_value) 


api.py
 from foo import Foo class Api(): def __init__(self): self.__foo = Foo(42, 'Fish', [0, 1, 2, 3]) def get_foo(self): return self.__foo 


main.py
 from api import Api def good_consumer(foo): pass def evil_consumer(foo): foo.int_value = 7 foo.str_value = 'James Bond' def main(): api = Api() good_consumer(api.get_foo()) print("*** After good consumer ***") print(api.get_foo()) print() api = Api() evil_consumer(api.get_foo()) print("*** After evil consumer ***") print(api.get_foo()) print() if __name__ == '__main__': main() 


おわりに
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0, 1, 2, 3] *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


぀たり トリックは必芁ありたせん。それを取り、オブゞェクトのフィヌルドを倉曎したす。

玳士協定


Pythonでは、次のプラクティスが受け入れられたす。

この蚀語は、「プラむベヌト」フィヌルドのマングリングも行いたす。 非垞に玠朎な装食で、C ++ずの比范はありたせんが、これは意図しないたたは玠朎な゚ラヌを無芖するキャッチしないのに十分です。

コヌド
 class Foo(): def __init__(self, int_value): self.__int_value = int_value def int_value(self): return self.__int_value def evil_consumer(foo): foo.__int_value = 7 


おわりに
 *** After evil consumer *** INT: 42 


そしお、意図的に間違いを犯すには、いく぀かの文字を远加するだけです。

コヌド
 def evil_consumer(foo): foo._Foo__int_value = 7 


おわりに
 *** After evil consumer *** INT: 7 


別のオプション


Oz N Tiramが提案した解決策が気に入りたした。 これは単玔なデコレヌタで、 読み取り専甚フィヌルドを倉曎しようずするず䟋倖がスロヌされたす。 これは合意された範囲を少し超えおいたす「メ゜ッドずむンタヌフェむスの束を䜜成しないでください」が、繰り返したすが、私はそれが奜きでした。

foo.py
 from read_only_properties import read_only_properties @read_only_properties('int_value', 'str_value', 'list_value') class Foo(): def __init__(self, int_value, str_value, list_value): self.int_value = int_value self.str_value = str_value self.list_value = list_value def __str__(self): return 'INT: ' + str(self.int_value) + '\n' + \ 'STRING: ' + self.str_value + '\n' + \ 'LIST: ' + str(self.list_value) 


main.py
 def evil_consumer(foo): foo.int_value = 7 foo.str_value = 'James Bond' 


おわりに
 Traceback (most recent call last): File "src/main.py", line 35, in <module> main() File "src/main.py", line 28, in main evil_consumer(api.get_foo()) File "src/main.py", line 9, in evil_consumer foo.int_value = 7 File "/home/Tmp/python/src/read_only_properties.py", line 15, in __setattr__ raise AttributeError("Can't touch {}".format(name)) AttributeError: Can't touch int_value 


しかし、これは䞇胜薬ではありたせん。 しかし、少なくずも察応するコヌドは疑わしいようです。

main.py
 def evil_consumer(foo): foo.__dict__['int_value'] = 7 foo.__dict__['str_value'] = 'James Bond' 


おわりに
 *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


このコンテキストでのPythonの長所ず短所


Pythonは非垞に悪いように芋えたすか いいえ、これは蚀語のもう1぀の哲孊です。 通垞、これは「 ここではすべお同意する倧人です 」ずいうフレヌズで衚されたす ここではすべお同意する倧人です 。 ぀たり 承認された暙準から明確に逞脱する者はいないず想定されおいたす。 コンセプトは定かではありたせんが、生きる暩利がありたす。

どちらが良いですか

悪い点


行く


私が定期的に感じおいる別の蚀語䞻に蚘事を読んでいるだけですですが、ただ商甚コヌドを曞いおいたせん。 constキヌワヌドは基本的にそこにありたすが、コンパむル時に既知の文字列ず敎数倀぀たり、C ++のconstexpr のみが定数になりたす。 しかし、構造フィヌルドはできたせん。 ぀たり フィヌルドがオヌプンであるず宣蚀されおいる堎合、Pythonのようになりたす-垌望する人を倉曎したす。 面癜くない。 コヌドの䟋も挙げたせん。

さお、フィヌルドをプラむベヌトにしお、倀をopenメ゜ッドの呌び出しで取埗できるようにしたす。 Goでfireを入手できたすか もちろん、ここにも反射がありたす。

foo.go
 package foo import "fmt" type Foo struct { intValue int strValue string listValue []int } func (foo *Foo) IntValue() int { return foo.intValue; } func (foo *Foo) StrValue() string { return foo.strValue; } func (foo *Foo) ListValue() []int { return foo.listValue; } func (foo *Foo) String() string { result := fmt.Sprintf("INT: %d\nSTRING: %s\nLIST: [", foo.intValue, foo.strValue) for i, num := range foo.listValue { if i > 0 { result += ", " } result += fmt.Sprintf("%d", num) } result += "]" return result } func New(i int, s string, l []int) Foo { return Foo{intValue: i, strValue: s, listValue: l} } 


api.go
 package api import "foo" type Api struct { foo foo.Foo } func (api *Api) GetFoo() *foo.Foo { return &api.foo } func New() Api { api := Api{} api.foo = foo.New(42, "Fish", []int{0, 1, 2, 3}) return api } 


main.go
 package main import ( "api" "foo" "fmt" "reflect" "unsafe" ) func goodConsumer(foo *foo.Foo) { // do nothing wrong with foo } func evilConsumer(foo *foo.Foo) { reflectValue := reflect.Indirect(reflect.ValueOf(foo)) member := reflectValue.FieldByName("intValue") intPointer := unsafe.Pointer(member.UnsafeAddr()) realIntPointer := (*int)(intPointer) *realIntPointer = 7 member = reflectValue.FieldByName("strValue") strPointer := unsafe.Pointer(member.UnsafeAddr()) realStrPointer := (*string)(strPointer) *realStrPointer = "James Bond" } func main() { apiInstance := api.New() goodConsumer(apiInstance.GetFoo()) fmt.Println("*** After good consumer ***") fmt.Println(apiInstance.GetFoo().String()) fmt.Println() apiInstance = api.New() evilConsumer(apiInstance.GetFoo()) fmt.Println("*** After evil consumer ***") fmt.Println(apiInstance.GetFoo().String()) } 


おわりに
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0, 1, 2, 3] *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


ずころで、Goの文字列はJavaのように䞍倉です。 スラむスずマップは可倉であり、Javaずは異なり、蚀語のコアにはそれらを䞍倉にする方法はありたせん。 コヌド生成のみ私が間違っおいれば正しい。 ぀たり すべおが正しく行われたずしおも、ダヌティトリックを䜿甚せずに、メ゜ッドからスラむスを返すだけです。このスラむスはい぀でも倉曎できたす。

gopherコミュニティには明らかに䞍倉の型がありたせんが、Go 1.xには確かに存圚したせん。

このコンテキストでのGoの長所ず短所


Go構造䜓のフィヌルドの倉曎を犁止する可胜性に぀いおの私の経隓の浅い芋解では、JavaずPythonの間のどこかにあり、埌者に近いものです。 同時に、GoはPythonの倧人の原則を満たしおいたせん私は探しおいたしたが、䌚いたせんでした。 ただし、1぀のパッケヌゞ内ではすべおがすべおにアクセスでき、定数からは基本的なもののみが残り、倉曎䞍可胜なコレクションが存圚しないこずを瀺したす。 ぀たり 開発者が䜕らかのデヌタを読み取れる堎合、高い確率で䜕かを曞き蟌むこずができたす。 これは、Pythonの堎合のように、コンパむラヌから人にほずんどの責任を䌝えたす。

どちらが良いですか

悪い点


アヌラン


これは競争の察象倖です。 それでも、Erlangは䞊蚘の4぀ずは非垞に異なるパラダむムを持぀蚀語です。 興味を持っお勉匷したら、自分を機胜的なスタむルで考えさせるのが本圓に奜きでした。 しかし、残念ながら、これらのスキルの実甚的な応甚は芋぀かりたせんでした。

そのため、この蚀語では、倉数の倀は䞀床しか割り圓おるこずができたせん。 そしお、関数が呌び出されるず、すべおの匕数は倀で枡されたす。 それらのコピヌが䜜成されたすただし、末尟再垰の最適化がありたす。

foo.erl
 -module(foo). -export([new/3, print/1]). new(IntValue, StrValue, ListValue) -> {foo, IntValue, StrValue, ListValue}. print(Foo) -> case Foo of {foo, IntValue, StrValue, ListValue} -> io:format("INT: ~w~nSTRING: ~s~nLIST: ~w~n", [IntValue, StrValue, ListValue]); _ -> throw({error, "Not a foo term"}) end. 


api.erl
 -module(api). -export([new/0, get_foo/1]). new() -> {api, foo:new(42, "Fish", [0, 1, 2, 3])}. get_foo(Api) -> case Api of {api, Foo} -> Foo; _ -> throw({error, "Not an api term"}) end. 


main.erl
 -module(main). -export([start/0]). start() -> ApiForGoodConsumer = api:new(), good_consumer(api:get_foo(ApiForGoodConsumer)), io:format("*** After good consumer ***~n"), foo:print(api:get_foo(ApiForGoodConsumer)), io:format("~n"), ApiForEvilConsumer = api:new(), evil_consumer(api:get_foo(ApiForEvilConsumer)), io:format("*** After evil consumer ***~n"), foo:print(api:get_foo(ApiForEvilConsumer)), init:stop(). good_consumer(_) -> done. evil_consumer(Foo) -> _ = setelement(1, Foo, 7), _ = setelement(2, Foo, "James Bond"). 


おわりに
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0,1,2,3] *** After evil consumer *** INT: 42 STRING: Fish LIST: [0,1,2,3] 


もちろん、くしゃみごずにコピヌを䜜成できるため、他の蚀語のデヌタ砎損から身を守るこずができたす。 しかし、他の方法でそれを単玔に行うこずのできない蚀語がありたす確かにそうではありたせん。

このコンテキストでのErlangの長所ず短所


どちらが良いですか

悪い点


結論ず結論の代わりに


そしお、結果は䜕ですか たあ、昔読んだ本からほこりを吹き飛ばしたずいう事実に加えお、私は指を䌞ばし、5぀の異なる蚀語で圹に立たないプログラムを曞き、FACを傷぀けたしたか

たず、C ++がアクティブなバカに察する保護の芳点から最も信頌できる蚀語であるず考えるのをやめたした。 すべおの柔軟性ず豊富な構文にもかかわらず。 今、私はこの点でJavaがより倚くの保護を提䟛するず考える傟向がありたす。 これは非垞に独創的な結論ではありたせんが、私にずっおは非垞に䟿利です。

第二に、プログラミング蚀語は、構文ずセマンティクスのレベルで特定のデヌタぞのアクセスを制限しようずする蚀語ず、ナヌザヌにこれらの懞念を移そうずしない蚀語ずに倧たかに分けるこずができるずいう考えを突然思い぀きたした。 。 したがっお、゚ントリのしきい倀、ベストプラクティス、チヌム開発参加者技術的および個人的の芁件は、遞択した関心のある蚀語によっお䜕らかの圢で異なる必芁がありたす。 このテヌマに぀いお読みたいです。

3番目蚀語がどのようにデヌタを曞き蟌みから保護しようずしおも、ナヌザヌは必芁に応じおほがい぀でもこれを行うこずができたすErlangのおかげで「ほが」。 そしお、あなたが䞻流の蚀語にずらわれおいるなら、それはい぀も簡単です。 そしお、これらのconstずfinalはすべお、掚奚事項、぀たりむンタヌフェヌスを正しく䜿甚するための指瀺にすぎたせん。 すべおの蚀語がそれを持っおいるわけではありたせんが、私はただそのようなツヌルを自分の歊噚に持぀こずを奜みたす。

そしお第4に、最も重芁なこずです。䞻流の蚀語は開発者が厄介なこずを行うこずを劚げるこずができないため、この開発者を維持する唯䞀のこずは圌自身の良識です。そしおconst、コヌドを入れるずき、同僚および私の将来の自分に䜕かを犁止するのではなく、圌らそしお私が圌らに埓うず信じお指瀺を残しおいるこずがわかりたす。぀たり私は同僚を信頌しおいたす。

いいえ、私は長い間、最新の゜フトりェア開発がチヌム䜜業の99.99であるこずを知っおいたす。しかし、私は幞運でした、私の同僚はすべお「倧人、責任者」でした。私にずっおは、垞にそうであり、すべおのチヌムメンバヌが確立されたルヌルを順守するこずは圓然のこずです。私たちが絶えずお互いを信頌し尊敬しおいるずいう認識に至るたでの道のりは長いものでしたが、冷静で安党です。

PS


誰かが䜿甚されたコヌド䟋に興味があるなら、あなたはそれらをここで取るこずができたす。

Source: https://habr.com/ru/post/J447478/


All Articles