Von einem Übersetzer: Dieser Artikel ist eine Übersetzung von
Material, das vom Programmierer
Alastair Paragas von Apple geschrieben wurde und mit Programmiersprachen wie Javascript, Python, PHP, Java, Scala, Haskell, Swift und Rust gearbeitet hat. Alastair teilt seine Gedanken zum Thema der Auswahl und des Lernens „seiner“ Sprache, da dieses Problem sowohl für Anfänger als auch für Profis relevant ist, die ein neues Toolkit auswählen möchten.
Egal, ob Sie eine Programmiersprache für eine Anstellung oder eine Fortbildung lernen oder es sich um ein reines Hobby handelt, früher oder später müssen Sie sich zwischen ihnen entscheiden. Wie kann man das machen? Die Frage ist nicht einfach, aber die Antwort lautet: Tausende von Programmierern tun es jeden Tag. Um Ihre Aufgabe zu erleichtern, sollten Sie einige Grundsätze befolgen.
Skillbox empfiehlt: Praktikum "Profession Web Developer" .
Wir erinnern Sie daran: Für alle Leser von Habr - ein Rabatt von 10.000 Rubel bei der Anmeldung für einen Skillbox-Kurs gemäß dem Habr-Aktionscode
.
Vergleichsindikatoren
AbstraktionsebenenWenn stark verallgemeinert, können wir sagen, dass moderne Programmiersprachen in drei Typen unterteilt sind:
- "Schnell", mit denen schnell Anwendungen oder deren Prototypen erstellt werden.
- "Infrastruktur", mit deren Hilfe Teile einer bereits geschriebenen Anwendung optimiert oder geändert werden können, um ihre Produktivität zu steigern.
- Die sogenannten Systemprogrammiersprachen, mit deren Hilfe Sie die vollständige Kontrolle über den Gerätespeicher erhalten.
Natürlich ist die tatsächliche Trennung der Typen zwischen den Programmiersprachen weniger streng: Es gibt intermediäre Hybridvarianten verschiedener Typen.
Wenn wir über das Erlernen von Sprachen sprechen, sollten Sie zuerst den ersten Typ ausprobieren - „schnelle“ Sprachen: Sie ermöglichen es Ihnen, das Ergebnis der Arbeit sofort zu sehen und aus Ihren eigenen Fehlern zu lernen. Dies sind hauptsächlich PHP, Javascript, Ruby und Python. Die Eintrittsschwelle ist hier minimal und Sie können die Grundlagen in kurzer Zeit erlernen. Diese Sprachen verfügen über Standardbibliotheken, mit denen Sie der Anwendung eine große Anzahl von Funktionen hinzufügen können, und der Umfang ihrer Funktionen ist recht groß.
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)
Implementieren von HTTP-Anforderungen mit mehreren Threads in Python mit statischer Typisierung. Multithreading bietet die Möglichkeit, drei Aufgaben zu wechseln (nennen wir sie Aufgaben A, B und C). Während eine Aufgabe (z. B. Aufgabe A) eine Operation im Zusammenhang mit E / A ausführt (und daher keine Berechnungsarbeit ausführt), werden andere Aufgaben gleichzeitig damit ausgeführt.Bei den "Infrastruktur" -Sprachen handelt es sich um Java, Kotlin, Scala, Clojure sowie GoLang, Swift und Haskell. Sie können sie bequem mit einer Strecke aufrufen, aber Sie können damit produktive Anwendungen erstellen. Zu den Komplexitäten gehören weniger sofort einsatzbereite Elemente, eine präzise Syntax usw. Diese Sprachen sind gut, weil Sie damit die Anwendung optimieren können. Wenn Sie Geschwindigkeit benötigen, schreiben Sie eine Anwendung auf eine von ihnen.
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()
Ein ähnliches Problem wurde bereits oben mit Python gelöst. Jetzt im Geschäft - Swift.Systemprogrammiersprachen - C, C ++, Rust. Sie bieten maximale Kontrolle über die Anwendung, einschließlich der Speicherverwaltung. Diese Sprachen eignen sich auch hervorragend zum Programmieren von Mikrocontrollern, Computern mit benutzerdefinierter Prozessorarchitektur und anderen Systemen. Niedrigsprachige Sprachen sind nach wie vor wichtig und werden höchstwahrscheinlich in naher Zukunft relevant bleiben.
FunktionalitätWie Sie wissen, dienen Sprachen als Mittel zur "Kommunikation" zwischen einem Computer und einem Programmierer. Damit diese Kommunikation reibungslos verläuft, lohnt es sich, die Syntax der Sprache im Detail zu studieren. Insbesondere sollte ein Spezialist die am häufigsten verwendeten Datenstrukturen kennen und verstehen, wie bestimmte Elemente seiner Anwendung geändert werden.
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 ist eine streng funktionale Programmiersprache. Er kann eingehende Datenstrukturen überprüfen und mit ihnen arbeiten, wenn sie bestimmte Anforderungen erfüllen.
Laufzeit - Sie müssen wissen, wie Ihre Anwendung auf verschiedenen Systemen funktioniert. Wird ein Sprachinterpreter benötigt (z. B. Python, NodeJS, PHP)? Wird eine systemabhängige Binärdatei generiert (z. B. Swift und GoLang)? Verwendet die ausgewählte Sprache eine Kombination aus der ersten und der zweiten Option, z. B. wird die Anwendung auf einer virtuellen Maschine (Java, Scala, Clojure) kompiliert und gestartet?
Übrigens wird auf dem Weg zu Spitzenleistungen dringend empfohlen, Docker plus zu studieren und zu verwenden, um die Linux-Administrationsprinzipien zu verstehen.
Bibliotheken - jede Sprache ist in bestimmten Situationen gut geeignet. Beispielsweise erfüllt Java viele der Anforderungen der Orchestrierung und Netzwerklogistik, einschließlich der Datenbankunterstützung durch Standardisierung der JDBC-Schnittstelle und von Projekten, wie sie von der Apache Foundation unterstützt werden. Gleiches gilt für Python - ideal für Datenanalysen und statistische Berechnungen - sowie für Haskell mit seinen Grammatiken, regulären Ausdrücken und Compilern. Die Popularität der Sprache und die Größe ihrer Community sind zwei weitere Argumente, die für die Verwendung eines bestimmten Programmierwerkzeugs in Ihrem Projekt sprechen. Wenn die Gemeinde klein ist, sollten Sie nicht auf die Ambulanzhilfe der Teilnehmer zählen. Und umgekehrt: Je größer die Community und je beliebter die Programmiersprache, desto schneller können Sie eine schwierige Aufgabe lösen oder sich von Kollegen beraten lassen.
MüllabfuhrDie Speicherbereinigung ist eine Form der automatischen Speicherverwaltung. Ein spezieller Prozess namens Garbage Collector gibt regelmäßig Speicher frei, indem Objekte gelöscht werden, die von Anwendungen nicht mehr benötigt werden. Jede Programmiersprache tut dies auf ihre eigene Weise.
Python implementiert die Referenzzählung über den Stop-the-World-Algorithmus. Es unterbricht die Programmausführung, startet und führt die Speicherbereinigung aus und setzt dann den Hauptprozess fort. Während der „Reinigung“ erscheinen 3 separate „Generationen“ - eine Reihe von „Müllhaufen“. Null enthält die „frischesten“ Objekte, dann folgen die Generationen 1 und 2.
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
Implementierung des Python-Garbage-Collectors
Die Ergebnisse des obigen ProgrammcodesPHP (ab Version PHP5.3) verwendet neben der Referenzzählung eine weitere Garbage Collection-Option. Hier wird dieser Vorgang ggf. zusammen mit dem Programm durchgeführt. Untergraphen, die von der Wurzel aus nicht erreichbar sind, werden entfernt.
Swift verwendet auch die Referenzzählung. Es gibt keine anderen Möglichkeiten, Müll zu sammeln. Der Fall wird unten gezeigt, wenn der "starke" Zähler des Objekts vollständig 0 erreicht und Person löscht (da er schwach mit Wohnung korreliert).
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") }

Es gibt viele Beispiele für Garbage Collection-Mechanismen, die in anderen Sprachen implementiert sind. Sie können die Gesamtleistung der Anwendung oder der Arbeit beeinträchtigen, ohne die Ausführung der Hauptaufgabe zu beeinträchtigen.
Doppelte Konzepte
Pakete erstellen und verwaltenMachen Sie sich mit den Mechanismen zum Speichern und Verfolgen von Abhängigkeiten sowie mit Möglichkeiten zum Verwalten von Informationen über die "Assembly" vertraut (Beschreibung des Pakets, Ausführen von Komponententests, Konfigurieren und Vorbereiten der Umgebung usw.).
Python verwendet
pip zusammen mit einer Datei " require.txt" zum Verwalten von Abhängigkeiten und
setup.py zum Verwalten von Umgebungseinstellungen . Haskell arbeitet mit
Cabal zusammen , um beide Probleme zu lösen. Java hat
Maven und
Gradle . Im Fall von Scala
SBT funktioniert PHP
Composer , NodeJS -
npm usw.
Stellen Sie sicher, dass Sie sich für die Lokalisierung der Entwicklungsumgebung entscheiden. Je nach Projekt möchten Sie möglicherweise verschiedene Versionen von Programmiersprachen ausführen. Phpbrew für PHP, pyenv für Python und nvm für NodeJS machen dies möglich.
Mit pyenv können Sie mit verschiedenen Versionen von Python arbeiten.In besonderen Fällen kommt es vor, dass die in einem Projekt verwendete Bibliothek automatisch in einem anderen installiert wird. Dies gilt insbesondere für Sprachen wie Python und Haskell. Um dieses Problem zu vermeiden, sollten Sie
virtualenv / venv für Python,
virtphp für PHP und
Cabal Sandboxes für Haskell verwenden.
Asynchrone Ein- / AusgabeDies ist eine Gelegenheit, die E / A-Leistung von Anwendungsdaten zu erhöhen. Gleichzeitig arbeitet jeder Thread mit einem eigenen Satz von Registern und Stapelinformationen.

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); }); }); }); }
Implementieren asynchroner E / A mit JavascriptFunktionsprogrammierungMit der funktionalen Programmierung können Sie dem Computer auf hoher Ebene "sagen", was Sie wirklich von ihm wollen. Die meisten Sprachen verfügen heutzutage über die grundlegendsten Funktionen, um dies zu implementieren: durch
Zuordnen ,
Filtern ,
Reduzieren für Listen usw. Aber es lohnt sich immer noch. Nachfolgend finden Sie ein Beispiel für eine funktionale Programmierung in einer Sprache, die eine solche Möglichkeit nicht zu implizieren scheint.
<?php
Schulung
Die erste Phase ist die Suche nach den erforderlichen Informationen zu speziellen Ressourcen und die Erstellung eines kleinen Projekts nach Abschluss der Grundausbildung. In den meisten Fällen können Sie Artikel wie „X in Y-Tagen lernen“ verwenden, von denen viele sehr gut sind. In vielen Fällen gibt es interaktive Trainingsbeispiele:
Eine Tour durch GoLang und
GoLang anhand eines Beispiels (für GoLang),
NodeSchool-Befehlszeilenübungen (für Javascript, nämlich NodeJS),
Scala-Übungen (für Scala),
Python Koans (für Python) usw. .p.
Mit etwas Kompliziertem zu beginnen, lohnt sich nicht. Das Erstellen kleiner Anwendungen und Skripte ist das, was ein Anfänger benötigt. Die Gesamtzahl der Codezeilen in solchen Experimenten überschreitet 300–400 nicht. Die Hauptsache, die in dieser Phase notwendig ist, ist, grundlegende Informationen zu erhalten, das Programmieren mit jeder normalen Geschwindigkeit zu lernen und das Wichtigste ist zu verstehen, was Sie tun.
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") } }
Ein Beispiel für ein erstes Skript, das einem unerfahrenen Programmierer eine Vorstellung davon gibt, wie sein Code funktioniertDie zweite Phase ist ein tieferes Studium der Sprache, die Schaffung eines vollwertigen Projekts, das nicht mehr als "Kinder" bezeichnet werden kann. In vielen Fällen müssen Sie sich mit der offiziellen Dokumentation vertraut machen. Für Javascript sind es
Mozilla Developer Docs , für Swift,
Swift Official Docs , für Java,
Java Learning Trails , für Python,
Python Official Docs t. Besondere Aufmerksamkeit sollte Online-Kursen mit guten Lehrern gewidmet werden.
Es lohnt sich auch, andere Open Source-Projekte zu erkunden. Ressourcen wie
Annotated jQuery Source oder
Annotated BackboneJS Source geben eine Vorstellung davon, wie eine bestimmte Programmiersprache und zusätzliche Bibliotheken in professionellen Projekten verwendet werden.
All dies hilft Ihnen dabei, Ihr eigenes ernstes Projekt zu erstellen, z. B. eine Desktop-Anwendung, eine Webanwendung oder ein mobiles Programm. Versuchen Sie, externe Bibliotheken zu verwenden, wenn Sie zusätzliche Tools und Funktionen benötigen.

Vergessen Sie nicht die Leistung Ihrer Anwendung, versuchen Sie immer, Informationen aus den neuesten Quellen zu beziehen. Die Lernmöglichkeiten sind endlos, Sie können sich für immer verbessern. Aber am Ende kann man sich von Anfang an als Profi fühlen - und es gibt einfach kein besseres Gefühl auf der Welt.
Skillbox empfiehlt: