Ilusi kekekalan dan kepercayaan sebagai dasar pengembangan tim

Secara umum saya adalah seorang programmer C ++. Yah, itu terjadi. Sebagian besar kode komersial yang saya tulis dalam karier saya adalah C ++. Saya tidak terlalu menyukai bias kuat pengalaman pribadi saya terhadap satu bahasa, dan saya berusaha untuk tidak melewatkan kesempatan untuk menulis sesuatu dalam bahasa lain. Dan majikan saya saat ini tiba-tiba memberikan kesempatan seperti itu: Saya berjanji untuk menjadikannya bukan utilitas yang paling sepele di Jawa. Pilihan bahasa implementasi dibuat karena alasan historis, dan saya tidak keberatan. Java jadi Java, semakin tidak familiar bagi saya - semakin baik.

Di antara hal-hal lain, saya punya tugas yang agak sederhana: sekali untuk membentuk satu set data yang terhubung secara logis dan mentransfernya ke konsumen tertentu. Mungkin ada beberapa konsumen, dan menurut prinsip enkapsulasi, kode transmisi (produsen) tidak tahu apa yang ada di dalamnya dan apa yang dapat dilakukan dengan sumber data. Tetapi pabrikan membutuhkan setiap konsumen untuk menerima data yang sama. Saya tidak ingin membuat salinan dan memberikannya. Ini berarti bahwa kita harus entah bagaimana menghilangkan peluang bagi konsumen untuk mengubah data yang dikirimkan kepada mereka.

Saat itulah pengalaman saya di Jawa terasa. Saya tidak memiliki fitur bahasa dibandingkan dengan C ++. Ya, ada kata kunci final sini, tetapi final Object seperti Object* const di C ++, bukan const Object* . Yaitu dalam final List<String> Anda dapat menambahkan baris, misalnya. Ini bisnis C ++: menempatkan const mana-mana sesuai dengan wasiat Myers, dan hanya itu! Tidak ada yang akan mengubah apa pun. Jadi? Yah, tidak juga. Saya memikirkan hal ini sedikit daripada melakukan utilitas pada waktu luang saya, dan itulah yang saya pikirkan.

C ++


Biarkan saya mengingatkan Anda tugas itu sendiri:

  1. Buat satu set data sekali.
  2. Jangan menyalin sesuatu yang tidak perlu.
  3. Cegah konsumen mengubah data ini.
  4. Minimalkan kode, mis. Jangan membuat banyak metode dan antarmuka untuk setiap set data yang diperlukan, secara umum, hanya di beberapa tempat.

Tidak ada kondisi yang memberatkan seperti multithreading, keamanan dalam arti pengecualian, dll. Pertimbangkan kasus yang paling sederhana. Inilah cara saya melakukannya dengan menggunakan bahasa yang paling akrab:

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; } } 


Jelas, semuanya baik-baik saja di sini, datanya tidak berubah.

Kesimpulan
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0, 1, 2, 3] 

Dan jika seseorang mencoba mengubah sesuatu?


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


Ya, kode itu tidak bisa dikompilasi.

Kesalahan
 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); 


Apa yang bisa salah?


Ini adalah C ++ - bahasa dengan gudang senjata yang kaya untuk menembak di kaki Anda sendiri! Sebagai contoh:

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


Sebenarnya semuanya:
 *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


Saya juga mencatat bahwa menggunakan reinterpret_cast bukan const_cast dalam hal ini akan menyebabkan kesalahan kompilasi. Tetapi para pemain dengan gaya C akan memungkinkan Anda untuk menghidupkan fokus ini.

Ya, kode tersebut dapat mengarah ke Perilaku Tidak Terdefinisi [C ++ 17 10.1.7.1/4] . Dia umumnya terlihat mencurigakan, yang bagus. Lebih mudah ditangkap saat ditinjau.

Sangat buruk bahwa kode jahat dapat bersembunyi di mana saja di dalam konsumen, tetapi tetap akan berfungsi:

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); } 


Kesimpulan
 *** After evil but cautious consumer *** INT: 42 STRING: Loki LIST: [0, 1, 2, 3] 


Keuntungan dan kerugian dari C ++ dalam konteks ini


Itu bagus:
  • Anda dapat dengan mudah mendeklarasikan akses baca ke apa pun
  • pelanggaran yang tidak sengaja dari pembatasan ini terdeteksi pada tahap kompilasi, karena objek konstan dan non-konstan dapat memiliki antarmuka yang berbeda
  • Pelanggaran sadar dapat dideteksi pada ulasan kode

Apa yang buruk:
  • pengelakan yang disengaja dari larangan perubahan adalah mungkin
  • dan dieksekusi dalam satu baris, yaitu mudah untuk melewatkan ulasan kode
  • dan dapat menyebabkan perilaku yang tidak terdefinisi
  • definisi kelas dapat meningkat karena kebutuhan untuk mengimplementasikan antarmuka yang berbeda untuk objek yang konstan dan tidak konstan


Jawa


Di Jawa, seperti yang saya pahami, pendekatan yang sedikit berbeda digunakan. Tipe primitif yang dideklarasikan sebagai final adalah konstan dalam arti yang sama seperti pada C ++. String di Java pada dasarnya tidak berubah, jadi final String adalah apa yang kita butuhkan dalam kasus ini.

Koleksi dapat ditempatkan dalam pembungkus yang tidak dapat diubah, yang di dalamnya terdapat metode statis dari kelas java.util.Collections - unmodifiableList , unmodifiableMap , dll. Yaitu Antarmuka untuk objek konstan dan tidak konstan adalah sama, tetapi objek tidak konstan melemparkan pengecualian ketika mencoba mengubahnya.

Adapun jenis kustom, pengguna sendiri harus membuat pembungkus abadi. Secara umum, inilah pilihan saya untuk 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(); } } } 


Kesimpulan
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0, 1, 2, 3] 


Upaya Ubah Gagal


Jika Anda hanya mencoba mengubah sesuatu, misalnya:

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


Kode ini akan dikompilasi, tetapi pengecualian akan diberikan saat runtime:

Pengecualian
 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) 


Usaha yang berhasil


Dan jika dengan cara yang buruk? Tidak ada cara untuk menghapus kualifikasi final dari jenisnya. Tetapi di Jawa ada hal yang jauh lebih kuat - refleksi.

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"); } 


Dan kekebalan berakhir
 *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


Kode tersebut terlihat lebih mencurigakan daripada cosnt_cast dalam C ++, bahkan lebih mudah untuk menangkap ulasan. Dan itu juga dapat menyebabkan efek yang tidak terduga (yaitu, apakah Java memiliki UB ?). Dan itu juga bisa bersembunyi secara mendalam.

Efek-efek yang tidak dapat diprediksi ini mungkin disebabkan oleh fakta bahwa ketika objek final diubah menggunakan refleksi, nilai yang dikembalikan oleh metode hashCode() mungkin tetap sama. Objek yang berbeda dengan hash yang sama tidak menjadi masalah, tetapi objek yang identik dengan hash yang berbeda adalah buruk.

Apa bahaya dari peretasan semacam itu di Jawa khusus untuk string ( contoh ): string di sini dapat disimpan di pool, dan tidak terkait satu sama lain, string yang sama dapat menunjukkan nilai yang sama di pool. Berubah satu - ubah semuanya.

Tapi! JVM dapat dijalankan dengan berbagai pengaturan keamanan. Security Manager default, diaktifkan, menekan semua trik di atas dengan refleksi:

Pengecualian
 $ 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) 


Keuntungan dan kerugian Jawa dalam konteks ini


Itu bagus:
  • ada kata kunci final yang entah bagaimana membatasi perubahan data
  • ada metode perpustakaan untuk mengubah koleksi menjadi tidak berubah
  • pelanggaran kekebalan sadar mudah dideteksi oleh ulasan kode
  • memiliki pengaturan keamanan JVM

Apa yang buruk:
  • upaya untuk mengubah objek yang tidak dapat diubah hanya akan muncul saat runtime
  • untuk membuat objek kelas tertentu tidak berubah, Anda harus menulis sendiri pembungkus yang sesuai
  • tanpa adanya pengaturan keamanan yang tepat, dimungkinkan untuk mengubah data yang tidak dapat diubah
  • tindakan ini dapat memiliki konsekuensi yang tidak terduga (walaupun mungkin itu baik - hampir tidak ada yang akan melakukan itu)


Python


Nah, setelah itu saya hanya tersapu gelombang keingintahuan. Bagaimana tugas-tugas tersebut diselesaikan, misalnya, dengan Python? Dan apakah mereka sudah memutuskan? Memang, dalam python pada prinsipnya tidak ada keteguhan, bahkan tidak ada kata kunci semacam itu.

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() 


Kesimpulan
 *** 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] 


Yaitu tidak diperlukan trik, ambil dan ubah bidang dari objek apa pun.

Perjanjian pria


Latihan berikut diterima dalam python:
  • bidang dan metode khusus yang namanya dimulai dengan satu garis bawah dilindungi (dan dilindungi di C ++ dan Java) bidang dan metode
  • bidang khusus dan metode dengan nama yang dimulai dengan dua garis bawah adalah bidang dan metode pribadi

Bahasanya bahkan membuat mangling untuk bidang "pribadi". Dekorasi yang sangat naif, tidak ada perbandingan dengan C ++, tapi ini cukup untuk mengabaikan (tetapi tidak menangkap) kesalahan yang tidak disengaja (atau naif).

Kode
 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 


Kesimpulan
 *** After evil consumer *** INT: 42 


Dan untuk membuat kesalahan dengan sengaja, tambahkan saja beberapa karakter.

Kode
 def evil_consumer(foo): foo._Foo__int_value = 7 


Kesimpulan
 *** After evil consumer *** INT: 7 


Pilihan lain


Saya menyukai solusi yang diusulkan oleh Oz N Tiram . Ini adalah dekorator sederhana yang ketika mencoba mengubah bidang hanya baca melempar pengecualian. Ini sedikit di luar ruang lingkup yang disepakati ("jangan membuat banyak metode dan antarmuka"), tapi, saya ulangi, saya menyukainya.

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' 


Kesimpulan
 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 


Tapi ini bukan obat mujarab. Tapi setidaknya kode yang sesuai terlihat mencurigakan.

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


Kesimpulan
 *** After evil consumer *** INT: 7 STRING: James Bond LIST: [0, 1, 2, 3] 


Kelebihan dan kekurangan Python dalam konteks ini


Apakah python tampak sangat buruk? Tidak, ini hanyalah filosofi bahasa lain. Biasanya ini diungkapkan dengan frasa " Kita semua orang dewasa yang setuju di sini " ( Kita semua orang dewasa yang setuju di sini ). Yaitu diasumsikan bahwa tidak ada seorang pun yang akan secara khusus menyimpang dari norma-norma yang diterima. Konsepnya tidak pasti, tetapi memiliki hak untuk hidup.

Itu bagus:
  • secara terbuka dinyatakan bahwa programmer harus memonitor hak akses, bukan compiler atau interpreter
  • ada konvensi penamaan yang diterima secara umum untuk bidang dan metode yang aman dan pribadi
  • beberapa pelanggaran akses mudah terdeteksi pada tinjauan kode

Apa yang buruk:
  • di tingkat bahasa tidak mungkin membatasi akses ke bidang kelas
  • semuanya bergantung pada niat baik dan kejujuran para pengembang
  • kesalahan hanya terjadi saat runtime


Pergi


Bahasa lain yang secara berkala saya rasakan (kebanyakan hanya membaca artikel), walaupun saya belum menulis sebaris kode komersial di situ. Kata kunci const pada dasarnya ada di sana, tetapi hanya string dan nilai integer yang diketahui pada waktu kompilasi (mis. constexpr dari C ++) dapat berupa konstanta. Tetapi bidang struktur tidak bisa. Yaitu jika bidang dinyatakan terbuka, maka ternyata seperti di python - ubah siapa yang Anda inginkan. Tidak menarik. Saya bahkan tidak akan memberikan contoh kode.

Nah, biarkan bidang bersifat pribadi, dan biarkan nilainya diperoleh melalui panggilan ke metode terbuka. Bisakah saya mendapatkan kayu bakar di Go? Tentu saja, ada juga refleksi di sini.

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()) } 


Kesimpulan
 *** 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] 


By the way, string di Go tidak dapat diubah, seperti di Jawa. Irisan dan peta bisa berubah, dan tidak seperti Jawa, tidak ada inti dalam bahasa untuk membuatnya tidak berubah. Hanya pembuatan kode (benar jika saya salah). Yaitu bahkan jika semuanya dilakukan dengan benar, jangan gunakan trik kotor, cukup kembalikan slice dari metode - slice ini selalu dapat diubah.

Komunitas gopher jelas tidak memiliki tipe yang tidak dapat diubah, tetapi tentu saja tidak akan ada dalam Go 1.x.

Keuntungan dan kerugian Go dalam konteks ini


Dalam pandangan saya yang tidak berpengalaman tentang kemungkinan melarang mengubah bidang struktur Go, itu adalah suatu tempat antara Jawa dan Python, lebih dekat ke yang terakhir. Pada saat yang sama, Go tidak (saya belum bertemu, meskipun saya sedang mencari) prinsip Python orang dewasa. Tetapi ada: di dalam satu paket semuanya memiliki akses ke semuanya, hanya sisa-sisa dari konstanta, keberadaan tidak adanya koleksi yang tidak dapat diubah. Yaitu jika pengembang dapat membaca beberapa data, maka dengan probabilitas tinggi dia dapat menulis sesuatu di sana. Yang, seperti dalam python, menyampaikan sebagian besar tanggung jawab dari kompiler kepada orang tersebut.

Itu bagus:
  • semua kesalahan akses terjadi selama kompilasi
  • trik kotor berbasis refleksi terlihat jelas di ulasan

Apa yang buruk:
  • sama sekali tidak ada konsep
  • tidak mungkin untuk membatasi akses ke bidang struktur dalam suatu paket
  • untuk melindungi bidang dari perubahan di luar paket, Anda harus menulis getter
  • semua koleksi referensi bisa berubah
  • dengan bantuan refleksi Anda bahkan dapat mengubah bidang pribadi


Erlang


Ini di luar kompetisi. Meski demikian, Erlang adalah bahasa dengan paradigma yang sangat berbeda dari empat di atas. Setelah saya mempelajarinya dengan penuh minat, saya benar-benar suka membuat diri saya berpikir dengan gaya fungsional. Tetapi, sayangnya, saya tidak menemukan aplikasi praktis dari keterampilan ini.

Jadi, dalam bahasa ini nilai variabel hanya dapat ditetapkan satu kali. Dan ketika fungsi dipanggil, semua argumen dilewatkan oleh nilai, yaitu salinannya dibuat (tetapi ada optimasi rekursi ekor).

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"). 


Kesimpulan
 *** After good consumer *** INT: 42 STRING: Fish LIST: [0,1,2,3] *** After evil consumer *** INT: 42 STRING: Fish LIST: [0,1,2,3] 


Tentu saja, Anda dapat membuat salinan untuk setiap bersin dan melindungi diri Anda dari korupsi data dalam bahasa lain. Tetapi ada bahasa (dan tentu saja bukan satu) di mana ia tidak bisa dilakukan dengan cara lain!

Keuntungan dan kerugian Erlang dalam konteks ini


Itu bagus:
  • data tidak dapat diubah sama sekali

Apa yang buruk:
  • menyalin, menyalin di mana-mana


Alih-alih kesimpulan dan kesimpulan


Dan apa hasilnya? Nah, selain fakta bahwa saya meniup debu dari beberapa buku yang saya baca sejak lama, saya mengulurkan jari, menulis program yang tidak berguna dalam 5 bahasa yang berbeda, dan menggoreskan FAQ?

Pertama, saya berhenti berpikir bahwa C ++ adalah bahasa yang paling dapat diandalkan dalam hal perlindungan terhadap orang bodoh yang aktif. Terlepas dari semua fleksibilitas dan sintaksinya yang kaya. Sekarang saya cenderung berpikir bahwa Java dalam hal ini memberikan lebih banyak perlindungan. Ini bukan kesimpulan yang sangat orisinal, tetapi bagi saya sendiri saya merasa sangat berguna.

Kedua, saya tiba-tiba merumuskan untuk diri saya sendiri gagasan bahwa bahasa pemrograman dapat secara kasar dibagi menjadi yang mencoba membatasi akses ke data tertentu pada tingkat sintaksis dan semantik, dan mereka yang bahkan tidak mencoba mengalihkan kekhawatiran ini kepada pengguna. . Oleh karena itu, ambang masuk, praktik terbaik, persyaratan untuk peserta pengembangan tim (baik teknis maupun pribadi) harus berbeda tergantung pada bahasa yang dipilih. Saya ingin membaca tentang hal ini.

Ketiga: tidak peduli bagaimana bahasa berusaha melindungi data dari penulisan, pengguna hampir selalu dapat melakukan ini jika diinginkan ("hampir" karena Erlang). Dan jika Anda membatasi diri pada bahasa umum - itu selalu mudah. Dan ternyata semua const dan final ini tidak lebih dari rekomendasi, instruksi untuk penggunaan antarmuka yang benar. Tidak semua bahasa memilikinya, tetapi saya masih lebih suka memiliki alat seperti itu di gudang senjata saya.

Dan keempat, hal yang paling penting: karena tidak ada bahasa (arus utama) yang dapat mencegah pengembang melakukan hal-hal buruk, satu-satunya hal yang membuat pengembang ini maju adalah kesopanannya sendiri. Dan ternyata, ketika saya memasukkan constkode saya, saya tidak melarang sesuatu kepada kolega saya (dan diri saya di masa depan), tetapi tinggalkan instruksinya, percaya bahwa mereka (dan saya) akan mengikuti mereka. YaituSaya percaya rekan-rekan saya.

Tidak, saya sudah lama mengetahui bahwa pengembangan perangkat lunak modern ada dalam 99,99% kasus kerja tim. Tapi saya beruntung, semua rekan saya adalah orang-orang "dewasa, bertanggung jawab". Bagi saya, itu selalu terjadi entah bagaimana, dan diterima begitu saja bahwa semua anggota tim mematuhi aturan yang ditetapkan. Jalan saya menuju kesadaran bahwa kita terus-menerus mempercayai dan menghormati satu sama lain adalah lama, tetapi sangat tenang dan aman.

PS


Jika seseorang tertarik pada contoh kode yang digunakan, Anda dapat membawanya ke sini .

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


All Articles