Pourquoi Go est mauvais pour les programmeurs peu intelligents

L'article a été rédigé en réponse à un article publié précédemment article antipode.

Pourquoi Go est mauvais pour les programmeurs peu intelligents

Au cours des deux dernières années, j'ai utilisé Go pour implémenter un serveur RADIUS spécialisé avec un système de facturation développé. En chemin, j’apprends les subtilités de la langue elle-même. Les programmes eux-mêmes sont très simples et ne constituent pas le but de cet article, mais l'expérience d'utilisation de Go elle-même mérite quelques mots pour sa défense. Go devient un langage de plus en plus courant pour un code sérieux et évolutif. Le langage a été créé par Google, où il est activement utilisé. En fin de compte, je pense honnêtement que la conception du langage Go est mauvaise pour les programmeurs non intelligents.

Conçu pour les programmeurs faibles ?

Les faibles parlent de problèmes. Le discours fort sur les idées et les rêves...

Go est très facile à apprendre, si simple que vous pouvez lire le code sans aucune formation. Cette fonctionnalité du langage est utilisée dans de nombreuses entreprises mondiales lorsque le code est lu avec des spécialistes non essentiels (managers, clients, etc.). Ceci est très pratique pour les méthodologies telles que le Design Driven Development.
Même les programmeurs débutants commencent à produire du code assez correct après une semaine ou deux. Le livre que j'ai étudié est « Go Programming » (de Mark Summerfield). Le livre est très bon, il aborde de nombreuses nuances de la langue. Après des langages inutilement compliqués comme Java, PHP, le manque de magie est rafraîchissant. Mais tôt ou tard, de nombreux programmeurs limités ont l'idée d'utiliser d'anciennes méthodes dans un nouveau domaine. Est-ce vraiment nécessaire ?

Rob Pike (le principal idéologue du langage) a créé le langage Go en tant que langage industriel facile à comprendre et efficace à utiliser. Le langage est conçu pour une productivité maximale dans les grandes équipes et cela ne fait aucun doute. De nombreux programmeurs débutants se plaignent de l’absence de nombreuses fonctionnalités. Ce désir de simplicité était une décision consciente de la part des concepteurs du langage, et afin de bien comprendre pourquoi cela était nécessaire, nous devons comprendre la motivation des développeurs et ce qu'ils essayaient de réaliser dans Go.

Alors pourquoi a-t-il été rendu si simple ? Voici quelques citations de Rob Pike :

Le point clé ici est que nos programmeurs ne sont pas des chercheurs. Ils sont généralement assez jeunes et viennent chez nous après des études, peut-être ont-ils étudié Java, ou C/C++, ou Python. Ils ne peuvent pas comprendre un bon langage, mais en même temps, nous voulons qu'ils créent de bons logiciels. C'est pourquoi la langue doit être facile à comprendre et à apprendre.

Il devrait être familier, à peu près similaire à C. Les programmeurs travaillant chez Google commencent leur carrière tôt et sont pour la plupart familiers avec les langages procéduraux, en particulier la famille C. L'exigence d'une productivité rapide dans un nouveau langage de programmation signifie que le langage ne doit pas être trop radical.

Des paroles sages, n'est-ce pas ?

Artefacts de simplicité

La simplicité est une condition nécessaire à la beauté. Lév Tolstoï.

Rester simple est l’un des objectifs les plus importants de toute conception. Comme vous le savez, un projet parfait n’est pas un projet où il n’y a rien à ajouter, mais un projet dans lequel il n’y a rien à retirer. Beaucoup de gens pensent que pour résoudre (ou même exprimer) des problèmes complexes, il faut un outil complexe. Cependant, ce n’est pas le cas. Prenons par exemple le langage PERL. Les idéologues du langage pensaient qu'un programmeur devait disposer d'au moins trois manières différentes de résoudre un problème. Les idéologues de la langue Go ont suivi une voie différente : ils ont décidé qu'une seule voie, mais très bonne, suffisait pour atteindre l'objectif. Cette approche repose sur des bases sérieuses : la seule manière est d’apprendre plus facilement et d’oublier plus difficilement.

De nombreux migrants se plaignent du fait que la langue ne contient pas d'abstractions élégantes. Oui, c’est vrai, mais c’est l’un des principaux avantages de la langue. Le langage contient un minimum de magie - aucune connaissance approfondie n'est donc requise pour lire le programme. Quant à la verbosité du code, ce n'est pas du tout un problème. Un programme Golang bien écrit se lit verticalement, avec peu ou pas de structure. De plus, la vitesse de lecture d'un programme est au moins d'un ordre de grandeur supérieure à la vitesse d'écriture. Si vous considérez que tout le code a un formatage uniforme (effectué à l'aide de la commande gofmt intégrée), alors la lecture de quelques lignes supplémentaires n'est pas du tout un problème.

Pas très expressif

L'art ne tolère pas que sa liberté soit restreinte. La précision n'est pas de sa responsabilité.

En raison du désir de simplicité, Go manque de constructions qui, dans d'autres langues, sont perçues comme quelque chose de naturel par les personnes qui y sont habituées. Au début, cela peut être quelque peu gênant, mais vous remarquerez ensuite que le programme est beaucoup plus facile et plus clair à lire.

Par exemple, un utilitaire de console qui lit stdin ou un fichier à partir des arguments de ligne de commande ressemblerait à ceci :

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

La solution du même problème en D, bien qu’elle semble un peu plus courte, n’est pas plus facile à lire

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

L'enfer de la copie

L'homme porte l'enfer en lui. Martin Luther.

Les débutants se plaignent constamment de Go en termes de manque de génériques. Pour résoudre ce problème, la plupart d’entre eux utilisent la copie directe de code. Par exemple, pour une fonction de sommation d'une liste d'entiers, ces futurs professionnels estiment que la fonctionnalité ne peut être implémentée autrement que par un simple copier-coller pour chaque type de données.

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

Le langage dispose de moyens suffisants pour mettre en œuvre de telles constructions. Par exemple, une programmation générique conviendrait.

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

Et, bien que notre code se soit avéré un peu plus long que le cas précédent, il s'est généralisé. Par conséquent, il ne nous sera pas difficile de mettre en œuvre toutes les opérations arithmétiques.

Beaucoup diront qu’un programme en D semble nettement plus court, et ils auront raison.

import std.stdio;
import std.algorithm;

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

Cependant, il est seulement plus court, mais pas plus correct, puisque l'implémentation de D ignore complètement le problème de la gestion des erreurs.

Dans la vraie vie, à mesure que la complexité de la logique augmente, l’écart se réduit rapidement. L'écart se réduit encore plus rapidement lorsque vous devez effectuer une action qui ne peut pas être effectuée à l'aide d'opérateurs de langage standard.

En termes de maintenabilité, d'extensibilité et de lisibilité, à mon avis, le langage Go gagne, même s'il perd en verbosité.

La programmation généralisée nous apporte dans certains cas des avantages indéniables. Ceci est clairement illustré par le package sort. Ainsi, pour trier n’importe quelle liste, il nous suffit d’implémenter l’interface 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)
}

Si vous prenez un projet open source et exécutez la commande grep « interface{} » -R, vous verrez à quelle fréquence des interfaces déroutantes sont utilisées. Des camarades fermés diront immédiatement que tout cela est dû au manque de génériques. Cependant, ce n'est pas toujours le cas. Prenons DELPHI comme exemple. Malgré la présence de ces mêmes génériques, il contient un type VARIANT spécial pour les opérations avec des types de données arbitraires. Le langage Go fait de même.

Du canon aux moineaux

Et il faut que le carcan soit à la taille de la folie. Stanislav Lec.

De nombreux fans extrêmes peuvent prétendre que Go dispose d'un autre mécanisme pour créer des génériques : la réflexion. Et ils auront raison... mais seulement dans de rares cas.

Rob Pike nous prévient :

Il s’agit d’un outil puissant qui doit être utilisé avec prudence. Cela doit être évité sauf si cela est strictement nécessaire.

Wikipédia nous dit ce qui suit :

La réflexion fait référence au processus au cours duquel un programme peut surveiller et modifier sa propre structure et son comportement pendant l'exécution. Le paradigme de programmation qui sous-tend la réflexion est appelé programmation réflexive. Il s'agit d'un type de métaprogrammation.

Cependant, comme vous le savez, il faut tout payer. Dans ce cas il s'agit de :

  • difficulté à écrire des programmes
  • vitesse d'exécution du programme

La réflexion doit donc être utilisée avec prudence, comme une arme de gros calibre. Une utilisation irréfléchie de la réflexion conduit à des programmes illisibles, des erreurs constantes et une faible vitesse. Juste ce qu'il faut pour qu'un programmeur snob puisse montrer son code devant d'autres collègues plus pragmatiques et modestes.

Un bagage culturel de Xi ? Non, dans plusieurs langues !

Outre la fortune, les dettes sont également laissées aux héritiers.

Malgré le fait que beaucoup pensent que le langage est entièrement basé sur l'héritage C, ce n'est pas le cas. Le langage intègre de nombreux aspects des meilleurs langages de programmation.

syntaxe

Tout d’abord, la syntaxe des structures grammaticales est basée sur la syntaxe du langage C. Cependant, le langage DELPHI a également eu une influence significative. Ainsi, on voit que les parenthèses redondantes, qui réduisent fortement la lisibilité du programme, ont été totalement supprimées. Le langage contient également l'opérateur « :=" inhérent au langage DELPHI. Le concept de packages est emprunté à des langages comme ADA. La déclaration des entités inutilisées est empruntée au langage PROLOG.

Sémantique

Les packages étaient basés sur la sémantique du langage DELPHI. Chaque package encapsule des données et du code et contient des entités privées et publiques. Cela vous permet de réduire l'interface du package au minimum.

La méthode d’opération de mise en œuvre par délégation a été empruntée au langage DELPHI.

Compilation

Ce n’est pas sans raison qu’il y a une blague : Go a été développé alors qu’un programme C était en cours de compilation. L’un des points forts du langage est sa compilation ultra-rapide. L'idée a été empruntée au langage DELPHI. Chaque package Go correspond à un module DELPHI. Ces packages ne sont recompilés que lorsque cela est vraiment nécessaire. Par conséquent, après la prochaine édition, vous n'avez pas besoin de compiler l'intégralité du programme, mais plutôt de recompiler uniquement les packages modifiés et les packages qui dépendent de ces packages modifiés (et même dans ce cas, uniquement si les interfaces des packages ont changé).

Constructions de haut niveau

Le langage contient de nombreuses constructions de haut niveau différentes qui ne sont en aucun cas liées aux langages de bas niveau comme C.

  • Cordes
  • Tables de hachage
  • Tranches
  • La typographie canard est empruntée à des langages comme RUBY (que, malheureusement, beaucoup ne comprennent pas ou n'utilisent pas à son plein potentiel).

Gestion de la mémoire

La gestion de la mémoire mérite généralement un article séparé. Si dans des langages comme C++, le contrôle est entièrement laissé au développeur, alors dans des langages ultérieurs comme DELPHI, un modèle de comptage de références a été utilisé. Avec cette approche, les références cycliques n'étaient pas autorisées, car des clusters orphelins étaient formés, Go dispose alors d'une détection intégrée de ces clusters (comme C#). De plus, le garbage collector est plus efficace que la plupart des implémentations actuellement connues et peut déjà être utilisé pour de nombreuses tâches en temps réel. Le langage lui-même reconnaît les situations dans lesquelles une valeur pour stocker une variable peut être allouée sur la pile. Cela réduit la charge sur le gestionnaire de mémoire et augmente la vitesse du programme.

Concurrence et concurrence

Le parallélisme et la compétitivité de la langue sont au-delà des éloges. Aucun langage de bas niveau ne peut rivaliser, même à distance, avec Go. Pour être honnête, il convient de noter que le modèle n’a pas été inventé par les auteurs du langage, mais simplement emprunté au bon vieux langage ADA. Le langage est capable de traiter des millions de connexions parallèles en utilisant tous les processeurs, tout en rencontrant des problèmes d'un ordre de grandeur moins complexes avec les blocages et les conditions de concurrence typiques du code multithread.

Des avantages supplémentaires

Si cela est rentable, tout le monde deviendra altruiste.

La langue nous offre également un certain nombre d’avantages incontestables :

  • Un seul fichier exécutable après la construction du projet simplifie grandement le déploiement des applications.
  • Le typage statique et l'inférence de type peuvent réduire considérablement le nombre d'erreurs dans votre code, même sans écrire de tests. Je connais des programmeurs qui se passent du tout d'écrire des tests et la qualité de leur code n'en souffre pas de manière significative.
  • Compilation croisée très simple et excellente portabilité de la bibliothèque standard, ce qui simplifie grandement le développement d'applications multiplateformes.
  • Les expressions régulières RE2 sont thread-safe et ont des temps d'exécution prévisibles.
  • Une bibliothèque standard puissante qui permet à la plupart des projets de se passer de frameworks tiers.
  • Le langage est suffisamment puissant pour se concentrer sur le problème plutôt que sur la manière de le résoudre, mais suffisamment bas pour que le problème puisse être résolu efficacement.
  • L'écosystème Go contient déjà des outils développés prêts à l'emploi pour toutes les occasions : tests, documentation, gestion des packages, linters puissants, génération de code, détecteur de conditions de course, etc.
  • La version 1.11 de Go a introduit la gestion intégrée des dépendances sémantiques, basée sur l'hébergement VCS populaire. Tous les outils qui composent l'écosystème Go utilisent ces services pour télécharger, créer et installer du code à partir d'eux d'un seul coup. Et c'est génial. Avec l'arrivée de la version 1.11, le problème de versionnage des packages a également été complètement résolu.
  • Parce que l'idée principale du langage est de réduire la magie, le langage incite les développeurs à gérer explicitement les erreurs. Et c’est exact, car sinon, il oubliera tout simplement la gestion des erreurs. Une autre chose est que la plupart des développeurs ignorent délibérément la gestion des erreurs, préférant plutôt que de les traiter, simplement transmettre l'erreur vers le haut.
  • Le langage n'implémente pas la méthodologie classique de la POO, puisque dans sa forme pure il n'y a pas de virtualité dans Go. Cependant, cela ne pose pas de problème lors de l'utilisation des interfaces. L'absence de POO réduit considérablement la barrière à l'entrée pour les débutants.

La simplicité au service de la communauté

C'est facile à compliquer, difficile à simplifier.

Go a été conçu pour être simple et il atteint cet objectif. Il a été écrit pour les programmeurs intelligents qui comprennent les avantages du travail d'équipe et qui en ont assez de la variabilité infinie des langages de niveau entreprise. Ayant un ensemble relativement restreint de structures syntaxiques dans son arsenal, il n'est pratiquement pas sujet à des changements au fil du temps, les développeurs ont donc beaucoup de temps libre pour le développement, et non pour étudier sans fin les innovations linguistiques.

Les entreprises bénéficient également de nombreux avantages : une faible barrière à l'entrée leur permet de trouver rapidement un spécialiste, et l'immuabilité de la langue leur permet d'utiliser le même code même après 10 ans.

Conclusion

La grande taille du cerveau n’a jamais fait d’un éléphant un lauréat du prix Nobel.

Pour les programmeurs dont l'ego personnel prime sur l'esprit d'équipe, ainsi que pour les théoriciens qui aiment les défis académiques et le "perfectionnement personnel" sans fin, le langage est vraiment mauvais, car il s'agit d'un langage artisanal à usage général qui ne permet pas d'obtenir plaisir esthétique du résultat de votre travail et montrez-vous professionnel devant des collègues (à condition que l'on mesure l'intelligence par ces critères, et non par le QI). Comme tout dans la vie, c'est une question de priorités personnelles. Comme toute innovation valable, le langage a déjà parcouru un long chemin depuis le déni universel jusqu’à l’acceptation massive. Le langage est ingénieux dans sa simplicité, et, comme vous le savez, tout ce qui est ingénieux est simple !

Source: habr.com

Ajouter un commentaire