Выбор языка программирования: 3 совета от программиста из Apple

в 11:29, , рубрики: skillbox, Блог компании Skillbox, выбор языка программирования, обучение, Программирование, Учебный процесс в IT, языки программирования

image

От переводчика: эта статья — перевод материала, написанного программистом Аластером Парагасом (Alastair Paragas) из Apple.Он работал с такими языками программирования, как Javascript, Python, PHP, Java, Scala, Haskell, Swift и Rust. Аластер делится собственными размышлениями на тему выбора и изучения «своего» языка, ведь этот вопрос актуален как для новичков, так и для профессионалов, которые хотят выбрать новый инструментарий.

Изучаете ли вы язык программирования ради трудоустройства или повышения квалификации или же это чистой воды хобби, рано или поздно вам придется выбирать между ними. Как это сделать? Вопрос непростой, но ответить на него можно так: каждый день это делают тысячи программистов. Чтобы облегчить себе задачу, стоит следовать нескольким принципам.

Skillbox рекомендует: Практический курс «Профессия Веб‑разработчик».
Напоминаем: для всех читателей Хабра — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр»

.

Сравнительные показатели

Уровни абстракции

Если сильно обобщить, можно сказать, что современные языки программирования разделяются на три типа:

  1. «Быстрые», которые используются для оперативного создания приложений или их прототипов.
  2. «Инфраструктурные», которые помогают оптимизировать или дорабатывать отдельные части уже написанного приложения для того, чтобы повысить его производительность.
  3. Так называемые системные языки программирования, использование которых позволяет получить в свое распоряжение полноценный контроль над памятью устройства.

Конечно, реальное разделение на типы среди языков программирования менее строгое: есть промежуточные, гибридные варианты различных типов.

Если говорить об изучении языков, то сначала стоит попробовать первый тип — «быстрые» языки: они позволяют сразу видеть результат работы и учиться на собственных ошибках. В первую очередь это PHP, Javascript, Ruby и Python. Порог входа здесь минимален, и научиться основам можно за короткий срок. У этих языков стандартные библиотеки, которые позволяют добавлять большое количество функций в приложение, а спектр их возможностей достаточно велик.

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)

Реализация многопоточных HTTP-запросов в Python со статической типизацией. Многопоточность предусматривает возможность чередования трех задач (назовем их задачами A, B и C). В то время как одна задача (скажем, задача A) выполняет некоторую операцию с привязкой к I / O (и, следовательно, не выполняет какую-либо работу по вычислению), одновременно с ней выполняются и другие задачи.

Что касается «инфраструктурных» языков, то это Java, Kotlin, Scala, Clojure, а также GoLang, Swift и Haskell. Назвать их удобными можно с натяжкой, но зато они позволяют создавать производительные приложения. К числу сложностей относится меньшее число элементов «из коробки», точный синтаксис и т.п. Эти языки хороши тем, что позволяют производить тонкую настройку приложения. Если вам необходима скорость работы, попробуйте написать приложение на одном из них.

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

Аналогичная задача уже решалась выше при помощи Python. Теперь в деле — Swift.

Системные языки программирования — С, С++, Rust. Они дают максимальный контроль над приложением, включая управление потреблением памяти. Также эти языки отлично подходят для программирования микроконтроллеров, компьютеров с нестандартной архитектурой процессора и других систем. Низкоуровневые языки до сих пор важны и скорее всего, будут оставаться актуальными и в ближайшем будущем.

Функциональность

Как известно, языки служат средством «общения» между компьютером и программистом. Для того чтобы это общение шло гладко, стоит изучить синтаксис языка в деталях. В частности, специалист должен знать наиболее часто используемые структуры данных и понимать, как можно модифицировать определенные элементы своего приложения.

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)
-- Filters Twitter tweets that are written only in English
filterEnglishTweets :: StreamingAPI -> Bool
filterEnglishTweets tweet =
  let
    langIsEnglish (Status {statusLang=language}) = case language of
      Just "en" -> True
      _ -> False
  in case tweet of
    SStatus statusObj -> langIsEnglish statusObj
    SRetweetedStatus (RetweetedStatus {rsRetweetedStatus=statusObj}) ->
      langIsEnglish statusObj
    _ -> False
 -- Filters Twitter tweets that are original posts
tweetParser :: StreamingAPI -> Maybe String
tweetParser tweet = case tweet of
  SStatus (Status {statusText=status}) -> Just $ unpack status
  SRetweetedStatus (RetweetedStatus {rsRetweetedStatus=rstatus}) ->
    Just $ unpack $ statusText rstatus
  _ -> Nothing
main :: IO ()
main = do 
    -- a bunch of connection setup details to Twitter
    {-
      Imagine a stream/production line of continually incoming Twitter tweets
      out of this stream, non-English tweets are removed
      each remaining tweet then gets packaged into one of two forms
        - one for original tweets
        - one for non-original tweets (retweets and whatnot)
      We then only grab packaged forms of original tweets and display them!
    -}
    in runResourceT $ do
      stream <- stream twitterInfo connectionManager apiRequest
      stream $=+
        Data.Conduit.List.filter filterEnglishTweets $=+
        Data.Conduit.List.map tweetParser $=+
        Data.Conduit.List.catMaybes $$+-
Data.Conduit.List.mapM_ (liftIO . putStrLn)

Haskell — строгий функциональный язык программирования. Он может проверять входящие структуры данных и работать с ними, если они соответствуют определенным требованиям.

Среда выполнения — необходимо знать, как ваше приложение будет работать на различных системах. Нужен ли языковой интерпретатор (например, Python, NodeJS, PHP)? Генерируется ли зависимый от архитектуры системы бинарник (например, Swift и GoLang)? Использует ли выбираемый язык сочетание первого и второго варианта, например, приложение компилируется и запускается на некоторой виртуальной машине (Java, Scala, Clojure)?
Кстати, на пути к совершенству очень рекомендуется изучить и начать использовать Docker плюс обязательно разобраться с принципами администрирования Linux.

Библиотеки — каждый язык хорошо подходит в определенных ситуациях. Например, Java соответствует множеству требований оркестровки и сетевой логистики, включая поддержку баз данных через стандартизацию интерфейса JDBC и проекты, подобные тем, которые подпадают под поддержку Apache Foundation. То же самое касается Python — он идеален для анализа данных и статистических подсчетов, — а также Haskell с его грамматиками, регулярными выражениями и компиляторами. Популярность языка и размер его сообщества — еще два аргумента, которые говорят в пользу использования определенного средства программирования в своем проекте. Если коммьюнити невелико, то на скорую развернутую помощь его участников рассчитывать не стоит. И, наоборот, чем больше сообщество и популярнее язык программирования, тем быстрее можно решить сложную задачу или получить совет от коллег.

Garbage Collection

«Сборка мусора» — одна из форм автоматического управления памятью. Специальный процесс, называемый garbage collector, периодически освобождает память, удаляя объекты, которые уже не будут востребованы приложениями. Каждый язык программирования выполняет это по-своему.

Python реализует подсчет ссылок посредством алгоритма «stop-the-world». Он приостанавливает выполнение программы, запускает и выполняет сборку мусора, затем возобновляет выполнение основного процесса. В ходе «уборки» фигурирует 3 отдельных «поколения» — комплекта «мусорных куч». Нулевое содержит самые «свежие» объекты, затем идут поколения 1 и 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

Реализация garbage collector на Python

Выбор языка программирования: 3 совета от программиста из Apple - 2
Результаты работы программного кода, указанного выше

В PHP (начиная с версии PHP5.3) используется еще один вариант сборки мусора наряду с подсчетом ссылок. Здесь этот процесс при необходимости выполняется вместе с программой. Подграфы, которые не могут быть достигнуты из корня, устраняются.

Swift также использует подсчет ссылок, иных способов сборки мусора здесь нет. Ниже показывается кейс, когда «сильный» счетчик объекта целиком доходит до 0 и очищается Person (поскольку он слабо соотносится с Apartment).

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

Выбор языка программирования: 3 совета от программиста из Apple - 3

Примеров механизмов сборки мусора, реализованных в других языках, множество. Они могут как затрагивать общую производительность приложения, так и работать без влияния на выполнение основной задачи.

Повторяющиеся концепции

Создание пакетов и управление ими

Знакомьтесь с механизмами хранения и отслеживания зависимостей, а также способами поддержания информации о «сборке» (описание пакета, порядок запуска модульных тестов, настройки и подготовки среды и т.п.).

Python использует pip in tandem with a requirements.txt file для управления зависимостями и setup.py для управления настройками окружения, Haskell работает с Cabal для решения обеих задач, у Java есть Maven и Gradle, в случае Scala работает SBT, PHP использует Composer, NodeJS — npm  и т.п.

Обязательно нужно определиться с локализацией окружения разработки — возможно, вы захотите запустить разные версии языков программирования в зависимости от проекта. Phpbrew для PHP, pyenv для Python и nvm для NodeJS дают возможность это сделать.

Выбор языка программирования: 3 совета от программиста из Apple - 4
Используя pyenv, можно работать с разными версиями Python.

В частных случаях бывает так, что библиотека, использованная в одном проекте, автоматически устанавливается в другие. Это актуально, в частности, для языков вроде Python и Haskell. Чтобы избежать этой проблемы, стоит использовать virtualenv/venv для Python, virtphp для PHP и Cabal Sandboxes для Haskell.

Выбор языка программирования: 3 совета от программиста из Apple - 5

Асинхронный ввод/вывод

Это возможность увеличить производительность ввода-вывода данных приложения. При этом каждый тред работает с собственным набором регистров и информации о стеке.

Выбор языка программирования: 3 совета от программиста из Apple - 6

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);
      });
    });
  });
}
 // One way we can proceed with execution
// Make one Promise out of a list of Promises
Promise.all(urlList.map(getSiteContents))
  .then(function (siteContents) {
    console.log("Promise based execution --->");
    console.log(siteContents);
  });
 // Another way we can proceed with execution
// "async" is an ES7 feature that makes our Promise/async I/O code look
// more synchronous
async function main () {
  const siteContents = await Promise.all(urlList.map(getSiteContents))
  console.log("Main() based execution --->");
  console.log(siteContents);
}
main();
// As Promises will happen in some future time, this will happen first
console.log("This console.log will most likely happen first");

Реализация асинхронного I/O с использованием Javascript

Функциональное программирование

Функциональное программирование позволяет «рассказать» компьютеру на высоком уровне, что вы от него, собственно, хотите. Большинство языков сегодня имеют самые базовые возможности для реализации этого: через map, filter, reduce for lists и т.п. Но это все равно стоит использовать. Ниже показан пример функционального программирования на языке, который вроде бы не предполагает такой возможности.

<?php
// Accumulator gets passed around for reuse - function as a value
$accumulator = function (
  string $accumulated_string,
  string $mapped_list_element
) {
  return $accumulated_string . $mapped_list_element . "n";
};
// Notice how array_map, array_filter and array_reduce
// accept functions as parameters - they are higher order functions
$mapped_array = array_reduce(
  array_map(
    function (int $list_element): string {
      return "A list element: " . $list_element;
    },
    [1, 2, 3, 4]
  ),
  $accumulator,
  ""
);
echo "Mapped Array: n";
echo $mapped_array;
$filtered_array = array_reduce(
  array_filter(
    [1, 2, 3, 4],
    function (int $list_element): bool {
      return $list_element > 2;
    }
  ),
  $accumulator,
  ""
);
echo "Filtered Array: n";
echo $filtered_array;
// Closures "enclose" over their surrounding state
// The $closure_incrementer function here returns a function
// making it a higher order function.
echo "Closure Incrementer: n";
$closure_incrementer = function () {
  $internal_variable = 0;
  return function () use (&$internal_variable) {
    return $internal_variable += 1;
  };
};
$instance = $closure_incrementer();
echo $instance() . " is equal to 1n";
echo $instance() . " is equal to 2n";

Обучение

Первый этап — поиск необходимой информации на специализированных ресурсах и создание небольшого проекта после того, как базовая подготовка пройдена. В большинстве случаев можно воспользоваться статьями вроде «Выучи Х за Y дней», многие из них весьма хороши. Во многих случаях есть интерактивные примеры для обучения: A Tour of GoLang и GoLang by example (для GoLang), NodeSchool Command Line exercises (для Javascript, а именно NodeJS), Scala Exercises (для Scala), Python Koans (для Python) и т.п.

Начинать с чего-то сложного не стоит. Создание небольших приложений и скриптов — то, что нужно новичку. Общее количество строк кода в таких опытах не превышает 300–400. Главное, что необходимо на этом этапе, — получить базовую информацию, научиться программировать со сколь-нибудь нормальной скоростью и самое важное — понимать, что вы делаете.

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

Пример начального скрипта, который дает представление начинающему программисту о том, как работает его код

Второй этап — более глубокое изучение языка, создание полноценного проекта, который уже нельзя назвать «детским». Во многих случаях необходимо знакомиться с официальной документацией. Для Javascript это Mozilla Developer Docs, для Swift — Swift Official Docs, для Java — Java Learning Trails, для Python — Python Official Docst. Особое внимание стоит обратить на онлайн-курсы с хорошими преподавателями.

Кроме того, стоит изучить другие проекты с открытым кодом. Ресурсы вроде Annotated jQuery source или Annotated BackboneJS source дают представление о том, как конкретный язык программирования и дополнительные библиотеки используются в профессиональных проектах.

Все это поможет создать собственный серьезный проект, например, десктопное приложение, веб-приложение, мобильную программу. Старайтесь использовать внешние библиотеки, когда вам нужны дополнительные инструменты и функции.

Выбор языка программирования: 3 совета от программиста из Apple - 7

Не забывайте о производительности своего приложения, всегда старайтесь черпать информацию из новейших источников. Возможности обучения бесконечны, совершенствоваться можно вечно. Но в конце концов вы можете почувствовать, как из новичка стали профессионалом — и лучшего ощущения в мире просто нет.

Skillbox рекомендует:

Автор: fokus-lop

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js