عموما أنا مبرمج C ++. حسنا حدث ما حدث. الغالبية العظمى من القانون التجاري الذي كتبته في حياتي المهنية هو C ++. لا أحب هذا التحيز القوي لتجربتي الشخصية تجاه لغة واحدة ، وأحاول ألا تفوت فرصة كتابة شيء بلغة أخرى. وفر صاحب العمل الحالي لي فجأة مثل هذه الفرصة: تعهدت بجعل واحدة ليست الأداة الأكثر تافهة في جاوة. لقد تم اختيار لغة التنفيذ لأسباب تاريخية ، ولم يكن لدي مانع. جافا حتى جافا ، وأقل مألوفة بالنسبة لي - كان ذلك أفضل.
من بين أمور أخرى ، كان لدي مهمة بسيطة إلى حد ما: مرة واحدة لتشكيل مجموعة معينة من البيانات المتصلة منطقيا ونقلها إلى مستهلك معين. يمكن أن يكون هناك العديد من المستهلكين ، ووفقًا لمبدأ التغليف ، لا يعرف رمز الإرسال (المنتج) ما هو بداخله وما يمكن أن يفعله بالبيانات المصدر. لكن الشركة المصنعة تحتاج كل مستهلك لتلقي نفس البيانات. لم أكن أرغب في عمل نسخ ومنحها. هذا يعني أنه يجب علينا حرمان المستهلكين بطريقة ما من فرصة لتغيير البيانات المرسلة إليهم.
في ذلك الوقت شعرت قلة خبرتي في جافا بحد ذاتها. لقد افتقرت إلى ميزات اللغة مقارنة بـ C ++. نعم ، هناك الكلمة الأساسية
final
هنا ، لكن
final Object
يشبه
Object* const
في C ++ ، وليس
const Object*
. أي في القائمة
final List<String>
يمكنك إضافة صفوف ، على سبيل المثال. إنه عمل سي + +: وضع
const
كل مكان وفقا لشهادة مايرز ، وهذا كل شيء! لا أحد سوف يغير شيئا. إلى هذا الحد؟ حسنا ، ليس حقا. لقد فكرت في هذا قليلاً
بدلاً من القيام بهذه الأداة في وقت فراغي ، وهذا ما جئت إليه.
C ++
دعني أذكرك بالمهمة نفسها:
- إنشاء مجموعة بيانات مرة واحدة.
- لا تنسخ أي شيء دون داع.
- منع المستهلك من تغيير هذه البيانات.
- تصغير الرمز ، أي لا تقم بإنشاء مجموعة من الطرق والواجهات لكل مجموعة من البيانات المطلوبة ، بشكل عام ، في مكانين فقط.
لا توجد ظروف مشددة مثل تعدد العمليات ، والأمن بمعنى الاستثناءات ، إلخ. النظر في أبسط الحالات. إليك كيفية القيام بذلك باستخدام اللغة الأكثر شيوعًا:
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; } }
من الواضح ، كل شيء على ما يرام هنا ، البيانات لم تتغير.
وإذا حاول شخص ما تغيير شيء ما؟
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"; }
وألاحظ أيضًا أن استخدام
reinterpret_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); }
مزايا وعيوب C ++ في هذا السياق
ما هو جيد:
- يمكنك أن تعلن بسهولة الوصول للقراءة إلى أي شيء
- تم الكشف عن انتهاك عرضي لهذا التقييد في مرحلة التجميع ، لأنه يمكن أن يكون للكائنات الثابتة وغير الثابتة واجهات مختلفة
- يمكن اكتشاف انتهاك واع على مراجعة الكود
ما هو سيء:
- التحايل المتعمد لحظر التغيير ممكن
- وتنفيذها في سطر واحد ، أي من السهل تخطي مراجعة الرمز
- ويمكن أن يؤدي إلى سلوك غير محدد
- يمكن تضخيم تعريف الفئة بسبب الحاجة إلى تنفيذ واجهات مختلفة لكائنات ثابتة وغير ثابتة
جافا
في Java ، كما أفهمها ، يتم استخدام نهج مختلف قليلاً. الأنواع البدائية المعلنة على أنها
final
ثابتة بنفس المعنى كما في C ++. الأوتار في Java غير قابلة للتغيير بشكل أساسي ، لذا فإن
final String
هي ما نحتاجه في هذه الحالة.
يمكن وضع المجموعات في غلافات غير قابلة للتغيير ، والتي توجد بها طرق ثابتة لفئة
java.util.Collections
- قائمة
unmodifiableList
قابلة للتعديل ، خريطة غير قابلة
unmodifiableMap
، إلخ. أي واجهة الكائنات الثابتة وغير الثابتة هي نفسها ، لكن الكائنات غير الثابتة تلقي استثناءًا عند محاولة تغييرها.
بالنسبة للأنواع المخصصة ، سيتعين على المستخدم نفسه إنشاء أغلفة ثابتة. بشكل عام ، هنا هو خياري لجافا.
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) {
فشل محاولة التغيير
إذا حاولت تغيير شيء ما ، على سبيل المثال:
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"); }
تبدو هذه الشفرة أكثر
cosnt_cast
من
cosnt_cast
في C ++ ، بل إنه من الأسهل
cosnt_cast
المراجعة. ويمكن أن يؤدي أيضًا إلى
تأثيرات غير متوقعة (على سبيل المثال ، هل تحتوي Java على
UB ؟). ويمكن أن تخفي أيضا بشكل تعسفي عميق.
قد تكون هذه الآثار غير المتوقعة بسبب حقيقة أنه عند تغيير الكائن
final
باستخدام الانعكاس ، قد تظل القيمة التي يتم إرجاعها بواسطة طريقة
hashCode()
هي. الكائنات المختلفة التي لها نفس التجزئة ليست مشكلة ، ولكن الأشياء المتماثلة ذات التجزئة المختلفة سيئة.
ما هو خطر مثل هذا الاختراق في جافا على وجه التحديد للسلاسل (
مثال ): يمكن تخزين السلاسل هنا في التجمع ، ولا علاقة لبعضها البعض ، فقط نفس السلاسل يمكن أن تشير إلى نفس القيمة في المجموعة. تغيير واحد - تغييرها جميعا.
ولكن! يمكن تشغيل
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)
مزايا وعيوب جافا في هذا السياق
ما هو جيد:
- هناك كلمة أساسية
final
تحد من تغيير البيانات بطريقة أو بأخرى - هناك طرق مكتبة لتحويل المجموعات إلى غير قابلة للتغيير
- تم اكتشاف انتهاك المناعة الواعي بسهولة من خلال مراجعة التعليمات البرمجية
- لديك إعدادات الأمان JVM
ما هو سيء:
- لن تظهر محاولة تغيير كائن ثابت إلا في وقت التشغيل
- من أجل جعل كائن من فئة معينة غير قابل للتغيير ، سوف تضطر إلى كتابة المجمع المناسب بنفسك
- في غياب إعدادات الأمان المناسبة ، من الممكن تغيير أي بيانات غير قابلة للتغيير
- يمكن أن يكون لهذا الإجراء عواقب لا يمكن التنبؤ بها (على الرغم من أنها ربما تكون جيدة - لن يقوم أي شخص تقريبًا بذلك)
الثعبان
حسنًا ، بعد ذلك اجتاحت ببساطة موجات الفضول. كيف يتم حل هذه المهام ، على سبيل المثال ، في بيثون؟ وهل قرروا على الإطلاق؟ في الواقع ، في الثعبان لا يوجد ثبات من حيث المبدأ ، حتى لا توجد مثل هذه الكلمات الرئيسية.
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()
أي ليست هناك حاجة إلى الحيل ، أعتبر وتغيير حقول أي كائن.
اتفاق الرجل المحترم
يتم قبول
الممارسة التالية في بيثون:
- الحقول والأساليب المخصصة التي تبدأ أسماؤها بشرطة سفلية واحدة محمية ( محمية في C ++ و Java)
- الحقول والأساليب المخصصة بأسماء تبدأ بخطين سفليين هي الحقول والأساليب الخاصة
اللغة تجعل من
التشويش على الحقول "الخاصة". زخرفة ساذجة جدًا ، لا تقارن بـ 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
ولرتكاب خطأ عن عمد ، فقط أضف بضعة أحرف.
قانون def evil_consumer(foo): foo._Foo__int_value = 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'
مزايا وعيوب بيثون في هذا السياق
هل يبدو أن الثعبان سيء جدًا؟ لا ، هذه مجرد فلسفة أخرى للغة. عادة ما يتم التعبير عنها بعبارة "
نحن جميعًا بالغون موافقون هنا " (
كلنا بالغون موافقون هنا ). أي من المفترض أن لا أحد سوف يحيد على وجه التحديد عن المعايير المقبولة. المفهوم غير مؤكد ، لكن له الحق في الحياة.
ما هو جيد:
- تم الإعلان صراحةً عن ضرورة قيام المبرمجين بمراقبة حقوق الوصول ، وليس المترجم أو المترجم الفوري
- يوجد اصطلاح تسمية مقبول بشكل عام للحقول والأساليب الآمنة والخاصة
- يتم اكتشاف بعض انتهاكات الوصول بسهولة في مراجعة التعليمات البرمجية
ما هو سيء:
- على مستوى اللغة ، يستحيل تقييد الوصول إلى حقول الفصل
- كل شيء يعتمد فقط على حسن النية والصدق من المطورين
- تحدث الأخطاء فقط في وقت التشغيل
العودة
لغة أخرى أشعر بها بشكل دوري (معظمها مجرد قراءة مقالات) ، على الرغم من أنني لم أكتب سطرًا من التعليمات البرمجية التجارية حتى الآن. الكلمة الأساسية
const
موجودة في الأساس ، لكن يمكن أن تكون السلاسل والقيم الصحيحة فقط المعروفة في وقت الترجمة (مثل
constexpr
من C ++) ثوابت. لكن حقول البنية لا تستطيع ذلك. أي إذا تم الإعلان عن فتح الحقول ، فسيظهر الأمر كما هو الحال في بيثون - قم بتغيير من تريد. رتيبا. لن أعطى رمزًا حتى.
حسنًا ، اسمح للحقول بأن تكون خاصة ، والسماح الحصول على قيمها من خلال المكالمات لفتح الطرق. هل يمكنني الحصول على الحطب في الذهاب؟ بالطبع ، هناك أيضا انعكاس هنا.
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) {
بالمناسبة ، السلاسل في Go غير قابلة للتغيير ، كما في Java. الشرائح والخرائط قابلة للتغيير ، وبخلاف Java ، لا توجد طريقة في جوهر اللغة لجعلها غير قابلة للتغيير. توليد الشفرة فقط (صحيح إذا كنت مخطئًا). أي حتى لو تم كل شيء بشكل صحيح ، لا تستخدم الحيل القذرة ، ما عليك سوى إرجاع الشريحة من الطريقة - يمكن دائمًا تغيير هذه الشريحة.
من الواضح أن مجتمع gopher
يفتقر إلى أنواع غير قابلة للتغيير ، ولكن بالتأكيد لن يكون هناك في Go 1.x.
مزايا وعيوب الذهاب في هذا السياق
في رأيي الذي لا يتمتع بالخبرة الكافية حول إمكانيات حظر تغيير حقول هياكل Go ، يكون في مكان ما بين Java و Python ، أقرب إلى الأخير. في الوقت نفسه ، لا تلتزم Go (لم أقابلها ، رغم أنني كنت أبحث عنها) بمبدأ Python الخاص بالبالغين. ولكن هناك: داخل حزمة واحدة كل شيء لديه حق الوصول إلى كل شيء ، تبقى فقط rudiment من الثوابت ، وجود غياب مجموعات غير قابلة للتغيير. أي إذا تمكن المطور من قراءة بعض البيانات ، فبإمكانية كبيرة يمكنه كتابة شيء ما هناك. والذي ، كما في بيثون ، ينقل معظم المسؤولية من المترجم إلى الشخص.
ما هو جيد:
- تحدث جميع أخطاء الوصول أثناء التحويل البرمجي
- الحيل القذرة القائمة على الانعكاس واضحة للعيان في المراجعة
ما هو سيء:
- لا يوجد ببساطة مفهوم
- من المستحيل تقييد الوصول إلى حقول البنية داخل الحزمة
- لحماية الحقول من التغييرات خارج الحزمة ، سوف تضطر إلى كتابة الحروف
- جميع المجموعات المرجعية قابلة للتغيير
- بمساعدة الانعكاس ، يمكنك حتى تغيير الحقول الخاصة
إرلانج
هذا هو خارج المنافسة. ومع ذلك ، فإن Erlang هي لغة ذات نموذج مختلف تمامًا عن الأربعة أعلاه. بمجرد دراستي باهتمام كبير ، أحببت حقًا أن أجعل نفسي أفكر بأسلوب وظيفي. لكن لسوء الحظ ، لم أجد تطبيقًا عمليًا لهذه المهارات.
لذلك ، في هذه اللغة ، يمكن تعيين قيمة المتغير مرة واحدة فقط. وعندما يتم استدعاء الوظيفة ، يتم تمرير جميع الوسائط حسب القيمة ، أي يتم إجراء نسخة منها (ولكن هناك تحسين العودية الذيل).
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").
بالطبع ، يمكنك عمل نُسخ لكل عطس وبالتالي حماية نفسك من تلف البيانات بلغات أخرى. ولكن هناك لغة (وبالتأكيد ليست لغة) حيث لا يمكن القيام بذلك بطريقة أخرى!
مزايا وعيوب إرلانج في هذا السياق
ما هو جيد:
- لا يمكن تغيير البيانات على الإطلاق
ما هو سيء:
بدلا من الاستنتاجات والاستنتاجات
وما هي النتيجة؟ حسنًا ، إلى جانب حقيقة أنني قمت بنسف الغبار من كتابين قرأتهما منذ فترة طويلة ، قمت بتمديد أصابعي ، وكتبت برنامجًا بلا فائدة بخمس لغات مختلفة ، وخدشت الأسئلة الشائعة؟
أولاً ، توقفت عن التفكير في أن لغة C ++ هي اللغة الأكثر موثوقية فيما يتعلق بالحماية من الخداع النشط. على الرغم من كل مرونتها وبناء الجملة الغنية. الآن أميل إلى الاعتقاد بأن جافا في هذا الصدد توفر المزيد من الحماية. هذا ليس استنتاجًا أصليًا للغاية ، لكنني أرى أنه مفيد للغاية بالنسبة لي.
ثانياً ، لقد صاغت بشكل مفاجئ بنفسي فكرة أن لغات البرمجة يمكن تقسيمها تقريبًا إلى تلك التي تحاول تقييد الوصول إلى بعض البيانات على مستوى بناء الجملة والدلالات ، وتلك التي لا تحاول حتى تحويل هذه المخاوف إلى المستخدمين . وفقًا لذلك ، ينبغي أن تختلف حد الدخول ، وأفضل الممارسات ، ومتطلبات المشاركين في تطوير الفريق (الفني والشخصي) بطريقة أو بأخرى اعتمادًا على لغة الاهتمام المختارة. أحب أن أقرأ عن هذا الموضوع.
ثالثًا: بغض النظر عن الطريقة التي تحاول بها اللغة حماية البيانات من الكتابة ، يمكن للمستخدم دائمًا القيام بذلك عند الرغبة ("تقريبًا" بسبب Erlang). وإذا كنت تقصر نفسك على اللغات السائدة - فمن السهل دائمًا. واتضح أن كل هذه
const
final
ليست أكثر من توصيات وتعليمات للاستخدام الصحيح للواجهات. ليست كل اللغات بها ، لكنني ما زلت تفضل امتلاك هذه الأدوات في ترسانتي.
-, : () , , — . , ,
const
, - ( ), , , ( ) . أي
.
, , — 99.99% . , «, » . - , , . ,
, , , .
PS
- ,
.