لماذا يعد Go أمرًا سيئًا للمبرمجين غير الأذكياء

تم كتابة المقال ردا على ما نشر سابقا المادة المضادة.

لماذا يعد Go أمرًا سيئًا للمبرمجين غير الأذكياء

على مدى العامين الماضيين، كنت أستخدم Go لتنفيذ خادم RADIUS متخصص بنظام فوترة متطور. وعلى طول الطريق، أتعلم تعقيدات اللغة نفسها. البرامج في حد ذاتها بسيطة للغاية وليست هي غرض هذه المقالة، لكن تجربة استخدام Go نفسها تستحق بضع كلمات للدفاع عنها. أصبحت Go لغة سائدة بشكل متزايد للرموز الجادة والقابلة للتطوير. تم إنشاء اللغة بواسطة Google، حيث يتم استخدامها بشكل نشط. خلاصة القول، أعتقد بصراحة أن تصميم لغة Go سيء بالنسبة للمبرمجين غير الذكيين.

مصممة للمبرمجين الضعفاء؟

الضعفاء يتحدثون عن المشاكل. الحديث القوي عن الأفكار والأحلام...

من السهل جدًا تعلم لغة Go، ومن السهل جدًا أن تتمكن من قراءة التعليمات البرمجية دون أي تدريب على الإطلاق. تُستخدم ميزة اللغة هذه في العديد من الشركات العالمية، عندما تتم قراءة التعليمات البرمجية مع متخصصين غير أساسيين (المديرين، العملاء، وما إلى ذلك). يعد هذا مناسبًا جدًا لمنهجيات مثل التطوير القائم على التصميم.
حتى المبرمجين المبتدئين يبدأون في إنتاج كود جيد جدًا بعد أسبوع أو أسبوعين. الكتاب الذي درست منه هو "Go Programming" (تأليف مارك سمرفيلد). الكتاب جيد جدًا ويتناول العديد من الفروق الدقيقة في اللغة. بعد اللغات المعقدة غير الضرورية مثل Java وPHP، أصبح الافتقار إلى السحر أمرًا منعشًا. ولكن عاجلاً أم آجلاً، لدى العديد من المبرمجين المحدودين فكرة استخدام الأساليب القديمة في مجال جديد. هل هذا ضروري حقا؟

ابتكر روب بايك (الإيديولوجي الرئيسي للغة) لغة Go كلغة صناعية سهلة الفهم وفعالة في الاستخدام. تم تصميم اللغة لتحقيق أقصى قدر من الإنتاجية في الفرق الكبيرة ولا شك في ذلك. يشكو العديد من المبرمجين المبتدئين من أن هناك العديد من الميزات التي يفتقدونها. كانت هذه الرغبة في البساطة قرارًا واعيًا من قبل مصممي اللغة، ومن أجل أن نفهم تمامًا سبب الحاجة إليها، يجب أن نفهم دوافع المطورين وما كانوا يحاولون تحقيقه في Go.

فلماذا تم جعل الأمر بهذه البساطة؟ فيما يلي بعض الاقتباسات من Rob Pike:

النقطة الأساسية هنا هي أن المبرمجين لدينا ليسوا باحثين. إنهم، كقاعدة عامة، صغار جدًا، يأتون إلينا بعد الدراسة، ربما درسوا Java، أو C/C++، أو Python. إنهم لا يستطيعون فهم لغة رائعة، ولكن في نفس الوقت نريد منهم إنشاء برامج جيدة. ولهذا السبب يجب أن تكون اللغة سهلة الفهم والتعلم.

يجب أن يكون مألوفًا، ويشبه تقريبًا C. يبدأ المبرمجون العاملون في Google حياتهم المهنية في وقت مبكر ويكونون في الغالب على دراية باللغات الإجرائية، ولا سيما عائلة C. إن متطلبات الإنتاجية السريعة في لغة برمجة جديدة تعني أن اللغة لا ينبغي أن تكون متطرفة للغاية.

كلمات حكيمة، أليس كذلك؟

التحف البساطة

البساطة شرط ضروري للجمال. ليف تولستوي.

يعد الحفاظ على البساطة أحد أهم الأهداف في أي تصميم. كما تعلمون، المشروع المثالي ليس مشروعًا لا يوجد فيه ما يمكن إضافته، ولكنه مشروع لا يوجد ما يمكن إزالته منه. يعتقد الكثير من الناس أنه من أجل حل (أو حتى التعبير عن) المشاكل المعقدة، هناك حاجة إلى أداة معقدة. ومع ذلك، فهو ليس كذلك. لنأخذ لغة PERL على سبيل المثال. يعتقد أيديولوجيو اللغة أن المبرمج يجب أن يكون لديه على الأقل ثلاث طرق مختلفة لحل مشكلة واحدة. اتخذ أيديولوجيو لغة الغو مسارًا مختلفًا؛ فقد قرروا أن طريقة واحدة، ولكنها جيدة حقًا، كانت كافية لتحقيق الهدف. ولهذا النهج أساس جدي: فالطريقة الوحيدة أسهل في التعلم وأصعب في نسيانها.

يشتكي العديد من المهاجرين من أن اللغة لا تحتوي على تجريدات أنيقة. نعم هذا صحيح، لكن هذه من أهم مميزات اللغة. تحتوي اللغة على الحد الأدنى من السحر - لذلك لا يلزم معرفة عميقة لقراءة البرنامج. أما بالنسبة لإسهاب الكود فهذه ليست مشكلة على الإطلاق. يقرأ برنامج Golang المكتوب بشكل جيد عموديًا، مع بنية قليلة أو معدومة. بالإضافة إلى ذلك، فإن سرعة قراءة البرنامج تكون على الأقل أكبر من سرعة كتابته. إذا كنت تعتقد أن كل التعليمات البرمجية لها تنسيق موحد (يتم ذلك باستخدام الأمر gofmt المضمن)، فإن قراءة بضعة أسطر إضافية لن تكون مشكلة على الإطلاق.

ليست معبرة جدا

الفن لا يتسامح عندما تكون حريته مقيدة. الدقة ليست مسؤوليته.

نظرًا للرغبة في البساطة، تفتقر لغة Go إلى التركيبات التي يُنظر إليها في اللغات الأخرى على أنها شيء طبيعي من قبل الأشخاص المعتادين عليها. في البداية، قد يكون الأمر غير مريح إلى حد ما، ولكن بعد ذلك لاحظت أن البرنامج أسهل بكثير وأكثر وضوحًا في القراءة.

على سبيل المثال، أداة وحدة التحكم التي تقرأ stdin أو ملف من وسيطات سطر الأوامر ستبدو كما يلي:

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
)

func main() {

    flag.Parse()

    scanner := newScanner(flag.Args())

    var text string
    for scanner.Scan() {
        text += scanner.Text()
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }

    fmt.Println(text)
}

func newScanner(flags []string) *bufio.Scanner {
    if len(flags) == 0 {
        return bufio.NewScanner(os.Stdin)
    }

    file, err := os.Open(flags[0])

    if err != nil {
        log.Fatal(err)
    }

    return bufio.NewScanner(file)
}

حل نفس المشكلة في D، على الرغم من أنه يبدو أقصر إلى حد ما، إلا أنه ليس من الأسهل قراءته

import std.stdio, std.array, std.conv;

void main(string[] args)
{
    try
    {
        auto source = args.length > 1 ? File(args[1], "r") : stdin;
        auto text   = source.byLine.join.to!(string);

        writeln(text);
    }
    catch (Exception ex)
    {
        writeln(ex.msg);
    }
}

جحيم النسخ

الإنسان يحمل الجحيم في داخله. مارتن لوثر.

يشكو المبتدئون باستمرار من Go فيما يتعلق بنقص الأدوية الجنيسة. لحل هذه المشكلة، يستخدم معظمهم النسخ المباشر للتعليمات البرمجية. على سبيل المثال، دالة لجمع قائمة من الأعداد الصحيحة، يعتقد هؤلاء المحترفون المحتملون أنه لا يمكن تنفيذ الوظيفة بأي طريقة أخرى غير النسخ واللصق البسيط لكل نوع بيانات.

package main

import "fmt"

func int64Sum(list []int64) (uint64) {
    var result int64 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func int32Sum(list []int32) (uint64) {
    var result int32 = 0
    for x := 0; x < len(list); x++ {
        result += list[x]
    }
    return uint64(result)
}

func main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(int32Sum(list32))
    fmt.Println(int64Sum(list64))
}

اللغة لديها وسائل كافية لتنفيذ مثل هذه الإنشاءات. على سبيل المثال، البرمجة العامة ستكون جيدة.

package main

import "fmt"

func Eval32(list []int32, fn func(a, b int32)int32) int32 {
    var res int32
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int32Add(a, b int32) int32 {
    return a + b
}

func int32Sub(a, b int32) int32 {
    return a + b
}

func Eval64(list []int64, fn func(a, b int64)int64) int64 {
    var res int64
    for _, val := range list {
        res = fn(res, val)
    }
    return res
}

func int64Add(a, b int64) int64 {
    return a + b
}

func int64Sub(a, b int64) int64 {
    return a - b
}

func main() {

    list32 := []int32{1, 2, 3, 4, 5}
    list64 := []int64{1, 2, 3, 4, 5}

    fmt.Println(Eval32(list32, int32Add))
    fmt.Println(Eval64(list64, int64Add))
    fmt.Println(Eval64(list64, int64Sub))
}

وعلى الرغم من أن الكود الخاص بنا كان أطول إلى حد ما من الحالة السابقة، إلا أنه أصبح معممًا. لذلك لن يكون من الصعب علينا تنفيذ جميع العمليات الحسابية.

سيقول الكثيرون أن البرنامج في D يبدو أقصر بكثير، وسوف يكونون على حق.

import std.stdio;
import std.algorithm;

void main(string[] args)
{
    [1, 2, 3, 4, 5].reduce!((a, b) => a + b).writeln;
}

ومع ذلك، فهو أقصر فقط، ولكنه ليس أكثر صحة، نظرًا لأن تطبيق D يتجاهل تمامًا مشكلة معالجة الأخطاء.

في الحياة الواقعية، مع زيادة تعقيد المنطق، تضيق الفجوة بسرعة. يتم إغلاق الفجوة بسرعة أكبر عندما تحتاج إلى تنفيذ إجراء لا يمكن تنفيذه باستخدام عوامل تشغيل اللغة القياسية.

فيما يتعلق بقابلية الصيانة والتوسعة وسهولة القراءة، في رأيي، تفوز لغة Go، على الرغم من أنها تخسر في الإسهاب.

البرمجة المعممة في بعض الحالات تعطينا فوائد لا يمكن إنكارها. يتم توضيح ذلك بوضوح من خلال حزمة الفرز. لذا، لفرز أي قائمة، نحتاج فقط إلى تنفيذ واجهةsort.Interface.

import "sort"

type Names []string

func (ns Names) Len() int {
    return len(ns)
}

func (ns Names) Less(i, j int) bool {
    return ns[i] < ns[j]
}

func (ns Names) Swap(i, j int) {
    ns[i], ns[j] = ns[j], ns[i]
}

func main() {
    names := Names{"London", "Berlin", "Rim"}
    sort.Sort(names)
}

إذا أخذت أي مشروع مفتوح المصدر وقمت بتشغيل الأمر grep "interface{}" -R، فسترى عدد المرات التي يتم فيها استخدام الواجهات المربكة. سيقول الرفاق ذوو التفكير المغلق على الفور أن كل هذا يرجع إلى نقص الأدوية الجنيسة. ومع ذلك، هذا ليس هو الحال دائما. لنأخذ دلفي كمثال. على الرغم من وجود هذه الأدوية العامة نفسها، إلا أنها تحتوي على نوع VARIANT خاص للعمليات التي تحتوي على أنواع بيانات عشوائية. لغة Go تفعل الشيء نفسه.

من مدفع إلى عصافير

ويجب أن تتناسب السترة مع حجم الجنون. ستانيسلاف ليك.

قد يدعي العديد من المعجبين المتطرفين أن Go لديها آلية أخرى لإنشاء الأدوية العامة - وهي الانعكاس. وسيكونون على حق...ولكن في حالات نادرة فقط.

يحذرنا روب بايك:

هذه أداة قوية يجب استخدامها بحذر. ويجب تجنبه إلا في حالة الضرورة القصوى.

تخبرنا ويكيبيديا بما يلي:

يشير الانعكاس إلى العملية التي يمكن للبرنامج من خلالها مراقبة وتعديل بنيته وسلوكه أثناء التنفيذ. يُطلق على نموذج البرمجة الكامن وراء الانعكاس اسم البرمجة الانعكاسية. هذا هو نوع من البرمجة الفوقية.

ومع ذلك، كما تعلم، عليك أن تدفع ثمن كل شيء. في هذه الحالة هو:

  • صعوبة في كتابة البرامج
  • سرعة تنفيذ البرنامج

لذلك، يجب استخدام الانعكاس بحذر، مثل سلاح من العيار الكبير. يؤدي الاستخدام الطائش للتفكير إلى برامج غير قابلة للقراءة وأخطاء مستمرة وسرعة منخفضة. إنه الشيء الوحيد الذي يمكن للمبرمج المتكبر أن يكون قادرًا على إظهار شفرته أمام زملائه الآخرين الأكثر واقعية وتواضعًا.

الأمتعة الثقافية من شي؟ لا، من عدد من اللغات!

جنبا إلى جنب مع الثروة، يتم ترك الديون أيضا للورثة.

على الرغم من أن الكثيرين يعتقدون أن اللغة تعتمد بالكامل على تراث لغة C، إلا أن الأمر ليس كذلك. تتضمن اللغة العديد من جوانب أفضل لغات البرمجة.

بناء الجملة

بادئ ذي بدء، يعتمد بناء جملة الهياكل النحوية على بناء جملة لغة C. ومع ذلك، كان للغة دلفي أيضًا تأثير كبير. وهكذا نرى أن الأقواس الزائدة عن الحاجة، والتي تقلل بشكل كبير من سهولة قراءة البرنامج، قد تمت إزالتها بالكامل. تحتوي اللغة أيضًا على عامل التشغيل ":=" المتأصل في لغة DELPHI. مفهوم الحزم مستعار من لغات مثل ADA. يتم استعارة إعلان الكيانات غير المستخدمة من لغة PROLOG.

دلالات

استندت الحزم إلى دلالات لغة DELPHI. تحتوي كل حزمة على البيانات والتعليمات البرمجية وتحتوي على كيانات خاصة وعامة. يتيح لك ذلك تقليل واجهة الحزمة إلى الحد الأدنى.

تم استعارة عملية التنفيذ بطريقة التفويض من لغة DELPHI.

التحويل البرمجي

ليس من قبيل الصدفة أن تكون هناك مزحة: لقد تم تطوير Go أثناء تجميع برنامج C. إحدى نقاط قوة اللغة هي تجميعها فائق السرعة. تم استعارة الفكرة من لغة دلفي. تتوافق كل حزمة Go مع وحدة DELPHI. تتم إعادة ترجمة هذه الحزم فقط عند الضرورة حقًا. لذلك، بعد التعديل التالي، لا تحتاج إلى ترجمة البرنامج بأكمله، بل تحتاج إلى إعادة ترجمة فقط الحزم المتغيرة والحزم التي تعتمد على هذه الحزم المتغيرة (وحتى ذلك الحين، فقط إذا تغيرت واجهات الحزمة).

بنيات رفيعة المستوى

تحتوي اللغة على العديد من التركيبات عالية المستوى المختلفة التي لا ترتبط بأي حال من الأحوال باللغات منخفضة المستوى مثل C.

  • خطوط
  • جداول التجزئة
  • شرائح
  • يتم استعارة كتابة البطة من لغات مثل RUBY (والتي، للأسف، لا يفهمها الكثيرون أو يستخدمونها إلى أقصى إمكاناتها).

إدارة الذاكرة

إدارة الذاكرة بشكل عام تستحق مقالة منفصلة. إذا تم ترك التحكم بالكامل في لغات مثل C++ للمطور، ففي اللغات اللاحقة مثل DELPHI، تم استخدام نموذج العد المرجعي. مع هذا النهج، لم يكن مسموحًا بالمراجع الدورية، حيث تم تشكيل مجموعات يتيمة، ثم قام Go باكتشاف هذه المجموعات بشكل مضمن (مثل C#). بالإضافة إلى ذلك، يعد جامع البيانات المهملة أكثر كفاءة من معظم التطبيقات المعروفة حاليًا ويمكن استخدامه بالفعل للعديد من المهام في الوقت الفعلي. تتعرف اللغة نفسها على المواقف التي يمكن فيها تخصيص قيمة لتخزين متغير على المكدس. يؤدي هذا إلى تقليل الحمل على مدير الذاكرة وزيادة سرعة البرنامج.

التزامن والتزامن

إن التوازي والقدرة التنافسية للغة أمر لا يستحق الثناء. لا يمكن لأي لغة منخفضة المستوى أن تنافس لغة Go عن بعد. لكي نكون منصفين، تجدر الإشارة إلى أن النموذج لم يخترعه مؤلفو اللغة، بل تم استعارته ببساطة من لغة ADA القديمة الجيدة. اللغة قادرة على معالجة الملايين من الاتصالات المتوازية باستخدام جميع وحدات المعالجة المركزية (CPU)، في حين تواجه مشكلات أقل تعقيدًا من حيث الحجم مع حالات التوقف التام وظروف السباق التي تعتبر نموذجية للتعليمات البرمجية متعددة الخيوط.

فوائد إضافية

إذا كان الأمر مربحًا، فسيصبح الجميع غير أنانيين.

توفر لنا اللغة أيضًا عددًا من الفوائد التي لا شك فيها:

  • يؤدي وجود ملف واحد قابل للتنفيذ بعد إنشاء المشروع إلى تبسيط عملية نشر التطبيقات إلى حد كبير.
  • يمكن أن تؤدي الكتابة الثابتة واستدلال الكتابة إلى تقليل عدد الأخطاء في التعليمات البرمجية بشكل كبير، حتى بدون كتابة الاختبارات. أعرف بعض المبرمجين الذين يستغنون عن كتابة الاختبارات على الإطلاق ولا تتأثر جودة أكوادهم بشكل كبير.
  • تجميع بسيط للغاية وقابلية نقل ممتازة للمكتبة القياسية، مما يبسط إلى حد كبير تطوير التطبيقات عبر الأنظمة الأساسية.
  • تعد التعبيرات العادية RE2 آمنة لمؤشر الترابط ولها أوقات تنفيذ يمكن التنبؤ بها.
  • مكتبة قياسية قوية تسمح لمعظم المشاريع بالاستغناء عن أطر عمل الطرف الثالث.
  • اللغة قوية بما يكفي للتركيز على المشكلة بدلاً من كيفية حلها، ومع ذلك فهي منخفضة المستوى بما يكفي لحل المشكلة بكفاءة.
  • يحتوي نظام Go eco بالفعل على أدوات مطورة جاهزة لجميع المناسبات: الاختبارات، والتوثيق، وإدارة الحزم، والأدوات القوية، وتوليد الأكواد، وكاشف ظروف السباق، وما إلى ذلك.
  • قدم الإصدار 1.11 من Go إدارة التبعية الدلالية المضمنة، والمبنية على قمة استضافة VCS الشهيرة. تستخدم جميع الأدوات التي يتكون منها نظام Go البيئي هذه الخدمات لتنزيل التعليمات البرمجية وإنشائها وتثبيتها بضربة واحدة. وهذا عظيم. مع وصول الإصدار 1.11، تم أيضًا حل مشكلة إصدار الحزمة بالكامل.
  • نظرًا لأن الفكرة الأساسية للغة هي تقليل السحر، فإن اللغة تحفز المطورين على معالجة الأخطاء بشكل صريح. وهذا صحيح، لأنه بخلاف ذلك، فإنه ببساطة سوف ينسى معالجة الأخطاء تمامًا. والشيء الآخر هو أن معظم المطورين يتجاهلون معالجة الأخطاء عمدًا، ويفضلون بدلاً من معالجتها إعادة توجيه الخطأ إلى الأعلى ببساطة.
  • لا تطبق اللغة منهجية OOP الكلاسيكية، لأنه في شكلها النقي لا توجد افتراضية في Go. ومع ذلك، هذه ليست مشكلة عند استخدام الواجهات. إن غياب OOP يقلل بشكل كبير من عائق الدخول للمبتدئين.

البساطة لصالح المجتمع

من السهل التعقيد، ومن الصعب التبسيط.

تم تصميم Go ليكون بسيطًا وينجح في تحقيق هذا الهدف. لقد تم كتابته للمبرمجين الأذكياء الذين يفهمون فوائد العمل الجماعي وقد سئموا من التنوع اللامتناهي للغات على مستوى المؤسسات. وجود مجموعة صغيرة نسبيًا من الهياكل النحوية في ترسانتها، فهي لا تخضع عمليًا للتغييرات بمرور الوقت، لذلك يتوفر للمطورين الكثير من الوقت المخصص للتطوير، وليس لدراسة الابتكارات اللغوية إلى ما لا نهاية.

تحصل الشركات أيضًا على عدد من المزايا: حاجز دخول منخفض يسمح لها بالعثور بسرعة على متخصص، كما أن ثبات اللغة يسمح لها باستخدام نفس الرمز حتى بعد 10 سنوات.

اختتام

حجم الدماغ الكبير لم يجعل أي فيل حائزًا على جائزة نوبل أبدًا.

بالنسبة لأولئك المبرمجين الذين الأسبقية للأنا الشخصية على روح الفريق، وكذلك المنظرين الذين يحبون التحديات الأكاديمية و"التحسين الذاتي" الذي لا نهاية له، فإن اللغة سيئة حقًا، لأنها لغة حرفية ذات أغراض عامة لا تسمح لك بالحصول عليها المتعة الجمالية نتيجة عملك وإظهار نفسك احترافيًا أمام الزملاء (بشرط أن نقيس الذكاء بهذه المعايير وليس بمعدل الذكاء). مثل كل شيء في الحياة، إنها مسألة أولويات شخصية. ومثل كل الابتكارات الجديرة بالاهتمام، قطعت اللغة بالفعل شوطا طويلا من الإنكار الشامل إلى القبول الجماعي. اللغة بارعة في بساطتها، وكما تعلمون، كل شيء عبقري بسيط!

المصدر: www.habr.com

إضافة تعليق