D'un traducteur: Cet article est une traduction de
matériel écrit par le programmeur
Alastair Paragas d'Apple et a travaillé avec des langages de programmation tels que Javascript, Python, PHP, Java, Scala, Haskell, Swift et Rust. Alastair partage ses réflexions sur le thème du choix et de l'apprentissage de «sa» langue, car cette question est pertinente pour les débutants comme pour les professionnels qui souhaitent choisir une nouvelle boîte à outils.
Que vous appreniez un langage de programmation pour l'emploi ou une formation avancée, ou que ce soit un pur passe-temps, vous devrez tôt ou tard choisir entre eux. Comment faire La question n'est pas simple, mais la réponse est: des milliers de programmeurs le font tous les jours. Pour faciliter votre tâche, il vaut la peine de suivre quelques principes.
Skillbox recommande: Cours pratique "Profession Web Developer" .
Nous vous rappelons: pour tous les lecteurs de Habr - une réduction de 10 000 roubles lors de l'inscription à un cours Skillbox selon le code promotionnel Habr
.
Indicateurs comparatifs
Niveaux d'abstractionS'ils sont fortement généralisés, nous pouvons dire que les langages de programmation modernes sont divisés en trois types:
- "Fast", qui sont utilisés pour créer rapidement des applications ou leurs prototypes.
- "Infrastructure", qui permet d'optimiser ou de modifier des parties d'une application déjà écrite afin d'augmenter sa productivité.
- Les soi-disant langages de programmation système, dont l'utilisation vous permet d'avoir un contrôle complet sur la mémoire de l'appareil.
Bien sûr, la véritable séparation des types entre les langages de programmation est moins stricte: il existe des variantes hybrides intermédiaires de différents types.
Si nous parlons d'apprentissage des langues, vous devez d'abord essayer le premier type - les langues «rapides»: elles vous permettent de voir immédiatement le résultat du travail et d'apprendre de vos propres erreurs. Ce sont principalement PHP, Javascript, Ruby et Python. Le seuil d'entrée ici est minimal et vous pouvez apprendre les bases en peu de temps. Ces langages ont des bibliothèques standard qui vous permettent d'ajouter un grand nombre de fonctions à l'application, et la gamme de leurs capacités est assez large.
from concurrent.futures import ThreadPoolExecutor from http.client import HTTPException from urllib import request from typing import Union, Dict, Any, List def get_request_task(url: str) -> Union[List[Dict[str, Any]], None]: try: contents = None with request.urlopen(url) as response: contents = response.read() return contents except HTTPException: return None with ThreadPoolExecutor() as executor: for result in executor.map(get_request_task, [ "https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments", "https://jsonplaceholder.typicode.com/albums" ]): if result is None: print("Something terrible has happened!") else: print(result)
Implémentation de requêtes HTTP multithread en Python avec typage statique. Le multithreading prévoit la possibilité d'alterner trois tâches (appelons-les tâches A, B et C). Alors qu'une tâche (par exemple, la tâche A) effectue une opération liée aux E / S (et, par conséquent, n'effectue aucun travail de calcul), d'autres tâches sont effectuées simultanément avec elle.Quant aux langages «infrastructure», ce sont Java, Kotlin, Scala, Clojure, ainsi que GoLang, Swift et Haskell. Vous pouvez les appeler pratiques avec un étirement, mais ils vous permettent de créer des applications productives. Les complexités incluent moins d'éléments prêts à l'emploi, une syntaxe précise, etc. Ces langues sont bonnes car elles vous permettent d'affiner l'application. Si vous avez besoin de vitesse, essayez d'écrire une application sur l'un d'eux.
import Foundation import Dispatch func getRequestTask(url: String, dispatchGroup: DispatchGroup) { dispatchGroup.enter() let request = URLRequest(url: URL(string: url)!) let task = URLSession(configuration: URLSessionConfiguration.default).dataTask( with: request, completionHandler: { (data, response, error) in if let data = data { if let dataAsString = String(data: data, encoding: .utf8) { print(dataAsString) dispatchGroup.leave() return } } print("Something terrible has happened!") dispatchGroup.leave() } ) task.resume() } let requestDispatchGroup = DispatchGroup() for url in [ "https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments", "https://jsonplaceholder.typicode.com/albums" ] { getRequestTask(url: url, dispatchGroup: requestDispatchGroup) } requestDispatchGroup.wait()
Un problème similaire a déjà été résolu ci-dessus en utilisant Python. Maintenant en affaires - Swift.Langages de programmation système - C, C ++, Rust. Ils offrent un contrôle maximal sur l'application, y compris la gestion de la mémoire. De plus, ces langages sont parfaits pour programmer des microcontrôleurs, des ordinateurs avec une architecture de processeur personnalisée et d'autres systèmes. Les langues de bas niveau sont toujours importantes et resteront très probablement pertinentes dans un avenir proche.
FonctionnalitéComme vous le savez, les langues servent de moyen de «communication» entre un ordinateur et un programmeur. Pour que cette communication se passe bien, il convient d'étudier en détail la syntaxe du langage. En particulier, un spécialiste doit connaître les structures de données les plus couramment utilisées et comprendre comment modifier certains éléments de son application.
module Main where import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Resource (runResourceT) import Data.Conduit (($$+-), ($=+), runConduit) import Data.Conduit.List (mapM_, map, filter, catMaybes) import Data.Text (unpack) import Data.Maybe (fromJust) import Web.Twitter.Types (StreamingAPI(SStatus, SRetweetedStatus) , Status(Status), statusText, statusLang , RetweetedStatus(RetweetedStatus), rsRetweetedStatus ) import Web.Twitter.Conduit.Stream (stream)
Haskell est un langage de programmation fonctionnel strict. Il peut vérifier les structures de données entrantes et travailler avec elles si elles répondent à certaines exigences.
Runtime - vous devez savoir comment votre application fonctionnera sur différents systèmes. Un interprète de langage est-il nécessaire (par exemple Python, NodeJS, PHP)? Un binaire dépendant du système est-il généré (par exemple Swift et GoLang)? La langue sélectionnée utilise-t-elle une combinaison des première et deuxième options, par exemple, l'application est compilée et lancée sur une machine virtuelle (Java, Scala, Clojure)?
Soit dit en passant, sur le chemin de l'excellence, il est fortement recommandé d'étudier et de commencer à utiliser Docker plus pour être sûr de comprendre les principes d'administration de Linux.
Bibliothèques - chaque langue est bien adaptée dans certaines situations. Par exemple, Java répond à de nombreuses exigences de l'orchestration et de la logistique de réseau, y compris la prise en charge des bases de données via la standardisation de l'interface JDBC et des projets comme ceux qui relèvent de la fondation Apache Foundation. Il en va de même pour Python - il est idéal pour l'analyse de données et les calculs statistiques - ainsi que Haskell avec ses grammaires, expressions régulières et compilateurs. La popularité de la langue et la taille de sa communauté sont deux autres arguments qui plaident en faveur de l'utilisation d'un certain outil de programmation dans votre projet. Si la communauté est petite, vous ne devriez pas compter sur l'assistance ambulancière de ses participants. Et vice versa, plus la communauté est grande et plus le langage de programmation est populaire, plus vite vous pouvez résoudre une tâche difficile ou obtenir des conseils de collègues.
Collecte des orduresLa récupération de place est une forme de gestion automatique de la mémoire. Un processus spécial appelé garbage collector libère périodiquement de la mémoire en supprimant les objets qui ne sont plus nécessaires aux applications. Chaque langage de programmation le fait à sa manière.
Python implémente le comptage de références via l'algorithme stop-the-world. Il suspend l'exécution du programme, démarre et exécute la récupération de place, puis reprend le processus principal. Pendant le «nettoyage», 3 «générations» distinctes apparaissent - un ensemble de «tas d'ordures». Zero contient les objets les plus «frais», puis les générations 1 et 2 suivent.
import gc import ctypes gc.set_debug(gc.DEBUG_SAVEALL) class PyObject(ctypes.Structure): _fields_ = [("refcnt", ctypes.c_long)] object1 = {} object2 = {} object3 = {} object1['reference_to_2'] = object2 object2['reference_to_1'] = object1 object3['some_key'] = 1 object1_memory_address = id(object1) object2_memory_address = id(object2) object3_memory_address = id(object3) print "Before garbage collection --->" print "Refcount for object1: {count}".format( count=PyObject.from_address(object1_memory_address).refcnt ) print "Refcount for object2: {count}".format( count=PyObject.from_address(object2_memory_address).refcnt ) print "Refcount for object3: {count}".format( count=PyObject.from_address(object3_memory_address).refcnt ) del object1, object2, object3 gc.collect() print "After garbage collection --->" print "Refcount for object1: {count}".format( count=PyObject.from_address(object1_memory_address).refcnt ) print "Refcount for object2: {count}".format( count=PyObject.from_address(object2_memory_address).refcnt ) print "Refcount for object3: {count}".format( count=PyObject.from_address(object3_memory_address).refcnt ) print "Objects that cannot be cleaned up by reference counting: --->" for x in gc.garbage: print x
Implémentation de garbage collector Python
Les résultats du code de programme ci-dessusPHP (à partir de la version PHP5.3) utilise une autre option de récupération de place avec le comptage des références. Ici, ce processus, si nécessaire, est effectué avec le programme. Les sous-graphes qui ne sont pas accessibles depuis la racine sont éliminés.
Swift utilise également le comptage des références; il n'y a pas d'autre moyen de collecter les ordures. Le cas est illustré ci-dessous lorsque le compteur "fort" de l'objet atteint entièrement 0 et efface Personne (car il est faiblement corrélé avec Appartement).
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") }

Il existe de nombreux exemples de mécanismes de récupération de place implémentés dans d'autres langues. Ils peuvent affecter les performances globales de l'application ou du travail sans affecter l'exécution de la tâche principale.
Concepts en double
Création et gestion de packagesFamiliarisez-vous avec les mécanismes de stockage et de suivi des dépendances, ainsi que les moyens de conserver les informations sur l '«assemblage» (description du package, comment exécuter des tests unitaires, configurer et préparer l'environnement, etc.).
Python utilise
pip en tandem avec un fichier requirements.txt pour gérer les dépendances et
setup.py pour gérer les paramètres d'environnement , Haskell travaille avec
Cabal pour résoudre les deux problèmes, Java a
Maven et
Gradle , dans le cas de Scala
SBT fonctionne, PHP utilise
Composer , NodeJS -
npm , etc.
Assurez-vous de décider de la localisation de l'environnement de développement - vous souhaiterez peut-être exécuter différentes versions des langages de programmation en fonction du projet. Phpbrew pour PHP, pyenv pour Python et nvm pour NodeJS rendent cela possible.
En utilisant pyenv, vous pouvez travailler avec différentes versions de Python.Dans des cas particuliers, il arrive que la bibliothèque utilisée dans un projet soit automatiquement installée dans d'autres. Cela est vrai, en particulier, pour des langages comme Python et Haskell. Pour éviter ce problème, vous devez utiliser
virtualenv / venv pour Python,
virtphp pour PHP et
Cabal Sandboxes pour Haskell.
Entrée / sortie asynchroneC'est l'occasion d'augmenter les performances d'E / S des données d'application. Dans le même temps, chaque thread fonctionne avec son propre ensemble de registres et d'informations de pile.

const https = require("https"); const urlList = [ "https://reqres.in/api/users?page=1", "https://reqres.in/api/users?page=2", "https://reqres.in/api/users?page=3" ]; function getSiteContents(url) { return new Promise(function (resolve, reject) { https.get(url, function (res) { var bodyData = ""; res.on("data", function (chunk) { bodyData += chunk; }); res.on("end", function () { resolve(bodyData); }); res.on("error", function (error) { reject(error.message); }); }); }); }
Implémentation d'E / S asynchrones à l'aide de JavascriptProgrammation fonctionnelleLa programmation fonctionnelle vous permet de «dire» à l'ordinateur à un niveau élevé ce que vous en voulez vraiment. La plupart des langues ont aujourd'hui les capacités les plus élémentaires pour l'implémenter: via la
carte , le
filtre , la
réduction pour les listes , etc. Mais cela vaut toujours la peine d'être utilisé. Voici un exemple de programmation fonctionnelle dans un langage qui ne semble pas impliquer une telle possibilité.
<?php
La formation
La première étape est la recherche des informations nécessaires sur les ressources spécialisées et la création d'un petit projet après la formation de base. Dans la plupart des cas, vous pouvez utiliser des articles comme «Learn X in Y Days», dont beaucoup sont très bons. Dans de nombreux cas, il existe des exemples de formation interactifs:
Un tour de GoLang et
GoLang par exemple (pour GoLang), des
exercices en ligne de commande NodeSchool (pour Javascript, à savoir NodeJS), des
exercices Scala (pour Scala),
Python Koans (pour Python), etc. .p.
Commencer par quelque chose de compliqué n'en vaut pas la peine. La création de petites applications et scripts est ce dont un débutant a besoin. Le nombre total de lignes de code dans de telles expériences ne dépasse pas 300–400. La principale chose qui est nécessaire à ce stade est d'obtenir des informations de base, d'apprendre à programmer à n'importe quelle vitesse normale et le plus important est de comprendre ce que vous faites.
func containedClosureIncrementer() -> (() -> Int) { var anInt = 0 func incrementer() -> Int { anInt = anInt + 1 return anInt } return incrementer } func containedClosureIncrementer2() -> () -> Int { var anInt = 0 return { anInt = anInt + 1 return anInt } } let closureIncrementer = containedClosureIncrementer() print("containedClosureIncrementer call - should be 1: \(closureIncrementer() == 1)") print("containedClosureIncrementer call - should be 2: \(closureIncrementer() == 2)") var someOptionalValue: Optional<String> = nil; print("Optional - someOptionalValue is null: \(someOptionalValue == nil)") someOptionalValue = "real value" print("Optional - someOptionalValue is 'real value' \(someOptionalValue == "real value")") (["real value", nil] as Array<Optional<String>>).forEach({ someOptionalValue in if let someValue = someOptionalValue { if someValue.hasPrefix("real") { print("someValue: has real") } else { print("someValue: doesn't have real") } } else { print("someValue: has nil") } }) if (someOptionalValue ?? "").hasPrefix("real") { print("Has real 2") } else { print("Doesn't have real") } let numbersList: [Int] = Array(1...10) print("List of numbers 1 to 10: \(numbersList)") let numbersListTimes2 = numbersList.map({ (someNumber: Int) -> Int in let multiplicand = 2 return someNumber * multiplicand }) let numbersListTimes2V2 = numbersList.map({ number in number * 2 }) let numbersListTimes2V3 = numbersList.map { $0 * 2 } print("List of numbers * 2: \(numbersListTimes2)") print("V1, V2 Map operations do the same thing: \(numbersListTimes2 == numbersListTimes2V2)") print("V1, V3 Map operations do the same thing: \(numbersListTimes2 == numbersListTimes2V3)") func testGuard() { let someOptionalValue: Optional<String> = nil; guard let someOptionalValueUnwrapped = someOptionalValue else { print("testGuard: Thrown exception - nil value") return } print("testGuard: no exception - non-nil value: \(someOptionalValueUnwrapped)") } testGuard() class RuntimeError: Error {} [{throw RuntimeError()}, {1} as () throws -> Int].forEach { let returnValue = try? $0() if let returnValueUnwrapped = returnValue { print("List of closures: A normal value was returned \(returnValueUnwrapped)") } else { print("List of closures: An error was thrown") } }
Un exemple de script initial qui donne une idée à un programmeur débutant sur le fonctionnement de son codeLa deuxième étape est une étude plus approfondie de la langue, la création d'un projet à part entière, que l'on ne peut plus appeler «enfant». Dans de nombreux cas, vous devez vous familiariser avec la documentation officielle. Pour Javascript, c'est
Mozilla Developer Docs , pour Swift,
Swift Official Docs , pour Java,
Java Learning Trails , pour Python,
Python Official Docs t. Une attention particulière devrait être accordée aux cours en ligne avec de bons enseignants.
Il vaut
également la peine d'explorer d'autres projets open source. Des ressources comme la
source jQuery annotée ou la
source BackboneJS annotée donnent une idée de la façon dont un langage de programmation particulier et des bibliothèques supplémentaires sont utilisés dans des projets professionnels.
Tout cela vous aidera à créer votre propre projet sérieux, par exemple, une application de bureau, une application Web, un programme mobile. Essayez d'utiliser des bibliothèques externes lorsque vous avez besoin d'outils et de fonctions supplémentaires.

N'oubliez pas les performances de votre application, essayez toujours de tirer des informations des dernières sources. Les possibilités d'apprentissage sont infinies, vous pouvez vous améliorer pour toujours. Mais au final, vous pouvez avoir l'impression d'être devenu un professionnel d'un débutant - et il n'y a tout simplement pas de meilleure sensation au monde.
Skillbox recommande: