Статьи

PHP против Ruby — давайте все вместе

Довольно часто вы видите разработчиков, которые имеют большой опыт работы с одним языком, пытаются поиграть с другим, а затем довольно быстро сравнивают их. Это сравнение, как правило, совершенно бесполезно, но заголовки кликов привлекают много трафика.

Вместо этого я подумал, что было бы интересно провести немного более справедливое сравнение с точки зрения того, кто действительно любит писать как на PHP, так и на Ruby, и делал это годами. Цель здесь не в том, чтобы выяснить, что «лучше», а в том, чтобы указать на несколько ключевых вещей, которые мне нравятся в Ruby и его экосистеме.

Снимок экрана 2015-11-21 01.20.42

Концептуальные различия

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

Имея это в виду, прежде чем я углублюсь в «кусочки Ruby, которые мне нравятся в сравнении», я думаю, что объяснение некоторых из этих концептуальных различий будет важным.

Метод, Переменная, Свойство?

PHP предлагает другой синтаксис для доступа к свойствам, методам или переменным. Руби нет.

PHP

$this - > turtle # Instance Property $this - > bike ( ) # Method $apple # Variable 

Рубин

 @turtle # Instance Property turtle # "Instance Property" using attr_reader: :turtle bike # Method apple # Variable 

Здесь педанты укажут, что attr_reader :turtle определит метод динамически, который используется как метод получения @turtle , делая turtle и bike одинаковыми. PHP-разработчик, который рассматривает использование turtle без явно заданного имени метода или переменной, будет невероятно озадачен тем, откуда она взялась.

Это не должно вызывать у вас проблемы слишком часто, но время от времени меня это ловит. Иногда люди в вашей команде могут изменить что-то из attr_reader на полный метод или наоборот, и это может привести к attr_reader .

Тем не менее, он может позволить некоторые действительно гибкие API, и позволяет вам делать некоторые дерзкие вещи, за которые вы благодарны в самый разгар.

Поле удалено, но загружен код, и контракт JSON все еще ожидает его появления?

 class Trip def canceled_at nil end end 

Это будет хорошо работать. Все, что вызывает trip.canceled_at , получит nil для этого поля, и это нормально. Мы удалим это позже.

Тип подсказки против утки

В мире PHP подсказки типа — странная и замечательная вещь. Такие языки, как Golang, абсолютно требуют, чтобы вы определяли тип для аргументов и возвращали типы. PHP добавил необязательный тип подсказки в 5.0 для аргументов. Вам могут потребоваться массивы, конкретные имена классов, интерфейсы или рефераты, а в последнее время — вызываемые объекты.

Наконец, PHP 7.0 позволяет использовать подсказки для возвращаемых типов, а также поддержку подсказок типа int , string , float и т. Д. Это было сделано с помощью подсказок скалярного типа RFC . Этот RFC был горько спорен и обсужден, но он попал туда, и это все еще совершенно необязательно; Хорошая новость для разнообразной пользовательской базы — PHP.

У Руби буквально ничего этого нет.

Duck Typing — это путь, который, как утверждают некоторые в мире PHP, является превосходным подходом.

Вместо того чтобы говорить: «Аргумент должен быть экземпляром класса, который реализует FooInterface», и зная, что FooInterface будет иметь FooInterface bar(int $a, array $b) , вы, по сути, говорите: «Аргумент может быть буквально любым, что отвечает на метод bar , и если это не так, мы можем сделать что-то еще ».

Рубин

 def read_data ( source ) return source . read if source . respond_to ? ( :read ) return File . read ( source . to_str ) if source . respond_to ? ( :to_str ) raise ArgumentError end filename = "foo.txt" read_data ( filename ) #=> reads the contents of foo.txt by calling # File.read() input = File . open ( "foo.txt" ) read_data ( input ) #=> reads the contents of foo.txt via # the passed in file handle 

Это действительно гибко, но для некоторых это запах кода. Особенно в таком языке, как PHP, где int(0) или int(1) считаются допустимыми логическими элементами в слабом режиме, принимая любое значение и просто надеясь, что он работает правильно, может быть страшным шагом.

В мире PHP мы могли бы просто определить два разных метода / функции:

 function read_from_filename ( string $filename ) { $file = new SplFileObject ( $filename , "r" ) ; return read_from_object ( $file ) ; } function read_from_object ( SplFileObject $file ) { return $file - > fread ( $file - > getSize ( ) ) ; } $filename = "foo.txt" ; read_from_filename ( $filename ) ; $file = new SplFileObject ( $filename , "r" ) ; read_from_object ( $file ) ; 

Тем не менее, если мы хотим сделать точно такой же подход к утилизации в PHP, мы легко можем:

 function read_data ( $source ) { if ( method_exists ( $source , 'read' ) ) { return $source - > read ( ) ; } elseif ( is_string ( $source ) ) { $file = new SplFileObject ( $source , "r" ) ) ; return $file - > fread ( $file - > getSize ( ) ) ; } throw new InvalidArgumentException ; } $filename = "foo.txt" ; read_data ( $filename ) ; #=> reads the contents of foo.txt by calling # SplFileObject->read(); $input = new SplFileObject ( "foo.txt" , "r" ) ; read_data ( $input ) ; #=> reads the contents of foo.txt via # the passed in file handle 

Вы можете сделать либо в PHP. PHP не волнует.

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

Тем не менее, есть много разработчиков PHP, которые категорически против подсказок типов, которые хотели бы, чтобы их вообще не было, и были расстроены, когда в PHP 7.0 было добавлено больше.

Интересно, что в Python, как и в Ruby, вообще отсутствовали подсказки типов. Тогда недавно они добавили их . Мне было бы интересно узнать, сколько людей перевернули стол за этот переход.

Fun Fun

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

Вложенные классы

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

Итак, у нас есть класс Box , который может генерировать ExplodingBoxException :

 namespace Acme \ Foo ; class Box { public function somethingBad ( ) { throw new Box\ ExplodingBoxException ; } } 

Это объявление класса исключения должно где-то жить. Мы могли бы поставить его на первое место в классе, но тогда у нас есть два класса в одном файле и… что многим это кажется немного смешным. Это также нарушает PSR-1, в котором говорится:

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

Итак, он идет в своем собственном файле:

 namespace Acme \ Foo \ Box ; class ExplodingBoxException { } 

Чтобы загрузить это исключение, мы должны нажать на автозагрузчик и снова зайти в файловую систему. Делать это не бесплатно! PHP 5.6 снижает накладные расходы для последующих запросов, если включен кэш кода операции, но это все еще дополнительная работа.

В Ruby вы можете вложить класс в другой класс:

 module Acme module Foo class Box class ExplodingBoxError < StandardError ; end def something_bad ! raise ExplodingBoxError end end end end 

Это доступно для определяющего класса и также доступно вне класса:

 begin box = Acme : :Foo : :Box . new box . something_bad ! rescue Acme : :Foo : :Box : :ExplodingBoxError # ... end 

Это может выглядеть немного странно, но довольно круто. Класс относится только к одному классу? Сгруппируйте их!

Другим примером будет миграция базы данных.

Миграции доступны во многих популярных средах PHP, от CodeIgniter до Laravel. Любой, кто использовал их часто, будет знать, что если вы ссылаетесь на модель или какой-либо другой класс в ваших миграциях, то позже вы измените этот класс, ваши старые миграции сломаются странными и чудесными способами.

Ruby прекрасно справляется с вложенными классами:

 class PopulateEmployerWithUserAccountName < ActiveRecord : :Migration class User < ActiveRecord : :Base belongs_to :account end class Account < ActiveRecord : :Base has_many :users end def up Account . find_each do | account | account . users . update_all ( employer : account . name ) end end def down # Update all users whose have account id to previous state # no employer set User . where . not ( account_id : nil ) . update_all ( employer : nil ) end end 

Вложенная версия моделей ORM User и Account будет использоваться вместо глобальных объявленных классов, что означает, что они больше похожи на моментальные снимки того времени, в котором они нам нужны. Это гораздо полезнее, чем вызывать код с движущимися стойками ворот, который может измениться в любой момент.

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

Разработчики PHP часто заканчивают тем, что для этого копируют сложную логику, или пишут SQL вручную, что является пустой тратой времени и усилий по сравнению с простым копированием соответствующих фрагментов модели.

дебаггер

XDebug — замечательная работа. Не поймите меня неправильно, использование точек останова удивительно и революционизирует способ отладки приложений PHP-разработчиками, выходя за пределы рабочего процесса отладки « var_dump() + refresh», который широко распространен среди var_dump() разработчиков.

Тем не менее, заставить XDebug работать с выбранной вами IDE, найти подходящий аддон, если он отсутствует, zend_extension=xdebug.so php.ini для правильной версии PHP, чтобы включить zend_extension=xdebug.so для вашего CLI и веб-версии, получить точки останова отправка, даже если вы используете Vagrant и т. д., может вызвать сильную боль в спине.

В Ruby немного другой подход. Как и в случае отладки JavaScript в браузере, вы можете просто вставить ключевое слово debugger в свой код и получить точку останова. В тот момент, когда ваш код выполняет эту строку — будь то $ rails server , модульный тест, интеграционный тест и т. Д., У вас будет экземпляр REPL для взаимодействия с вашим кодом.

Вокруг есть несколько отладчиков. Один популярный отладчик называется pry , а другой — byebug . Они оба гемы, их можно установить через Bundler, добавив их в свой Gemfile :

 group :development , :test do gem "byebug" end 

Это эквивалентно зависимости dev Composer, и после установки вы можете просто вызвать debugger если вы используете Rails. Если нет, вам нужно сначала require "byebug" .

Удобное руководство по Rails Отладка приложений Rails , показывает, как все выглядит, если вы вставляете ключевое слово в свое приложение:

 [1, 10] in /PathTo/project/app/controllers/articles_controller.rb 3: 4: # GET /articles 5: # GET /articles.json 6: def index 7: byebug => 8: @articles = Article.find_recent 9: 10: respond_to do |format| 11: format.html # index.html.erb 12: format.json { render json: @articles } (byebug) 

Стрелка показывает строку, в которой работает экземпляр REPL, и вы можете выполнить код прямо там. На данный момент @articles еще не определен, но вы можете вызвать Article.find_recent чтобы увидеть, что происходит. Если произойдет ошибка, вы можете либо нажать « next чтобы перейти к следующей строке в том же контексте, либо перейти к следующей инструкции, которая будет выполнена.

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

Делать это в своих тестах просто потрясающе.

Если не

Многим не нравится unless . Его часто злоупотребляют, как и многими функциями многих языков программирования, и, unless не считать, что многие годы заводили людей, как указывается в этой статье 2008 года.

Управляющая структура unless что противоположна if . Вместо выполнения блока кода, когда условие оценивается как true , оно будет оцениваться только тогда, когда условие оценивается как false .

 unless foo ? # bar that thing end # Or def something return false unless foo ? # bar that thing end 

Это немного облегчает работу, особенно когда есть несколько условий, возможно, || и несколько скобок. Вместо того, чтобы переключать его на ура, вот так: if ! (foo || bar) && baz if ! (foo || bar) && baz вы можете просто сделать, unless (foo || bar) && baz .

Теперь, это может быть немного дороже, и никто на работе не позволил бы вам отправлять « unless с else , но «разве что» само по себе является удобной функцией.

Когда в 2007 году люди запрашивали эту функцию для PHP, она некоторое время игнорировалась, пока создатель PHP Расмус Лердорф не сказал, что это будет BC-перерыв для всех, у кого есть функция unless() , и это будет «не очевидно для не говорящих по-английски» такие люди, как я.

Это странное слово, которое по существу означает «нет», хотя даже если оно логически должно быть эквивалентно «больше», так как в противоположность «больше» будет «меньше», и вставка «un» перед ним внезапно полностью изменит значение полностью ,

Я не согласен с этим и до сих пор. Люди, которые читают, unless не собираются думать, что это «противоположность меньшему» просто на основе un . Если бы это было так, люди читали бы функцию uniqid() и думали, что она противоположна iqid() .

Мы предложили столько же, и нам сказали, что мы «просто глупы». С тех пор это было названо wontfix .

Методы предикатов

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

В Ruby есть много встроенных, таких как object.nil? , Это в основном $object === nil в PHP. Включать include? вместо include также гораздо понятнее то, что он задает вопрос, а не выполняет действие.

Определить свое собственное довольно круто:

 class Rider def driver ? ! potential_driver . nil ? && vehicle . present ? end end 

Многие PHP-разработчики делают это путем добавления префикса имени метода к is и / или has , поэтому вместо этого у вас есть isDriver() и, возможно, hasVehicle() , но иногда вам приходится использовать другие префиксы. Метод, который я написал в Ruby, который был can_drive? было бы canDrive() в PHP, и это не ясно, что это метод предиката. Мне нужно было бы переименовать его в isAbleToDrive() или что-то вроде болтовни, чтобы прояснить это с традицией is / has место в мире PHP.

Еще более короткий синтаксис массива

В PHP определение литеральных массивов легко и стало намного менее многословным в PHP 5.4 с добавлением синтаксиса короткого массива:

 // < 5.4 $a = array ( 'one' = > 1 , 'two' = > 2 , 'three' = > 'three' ) ; // >= 5.4 $a = [ 'one' = > 1 , 'two' = > 2 , 'three' = > 'three' ] ; 

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

 $a = [ 'one' : 1 , 'two' : 2 , 'three' : 'three' ] ; 

Это довольно хорошо, если вы спросите меня, и когда вы получаете вложенные массивы, это действительно экономит немного времени, когда вы делаете это несколько сотен раз в день.

Шон Коутс предложил это с RFC еще в 2011 году, но он так и не сделал этого.

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

Объектные литералы

В том же RFC, указанном выше, подчеркивается особенность Ruby, которую я хотел бы видеть в PHP: объектные литералы. В PHP, если вы хотите определить StdClass со значениями, у вас есть два подхода:

 $esQuery = new stdClass ; $esQuery - > query = new stdClass ; $esQuery - > query - > term = new stdClass ; $esQuery - > query - > term - > name = 'beer' ; $esQuery - > size = 1 ; // OR $esQuery = ( object ) array ( "query" = > ( object ) array ( "term" = > ( object ) array ( "name" = > "beer" ) ) , "size" = > 1 ) ; 

Я знаю, что так было всегда в PHP, но это может быть намного проще.

Предложенный синтаксис в RFC почти полностью соответствует Ruby:

PHP

 $esQuery = { "query" : { "term" : { "name" : "beer" } } , "size" : 1 } ; 

Рубин

 esQuery = { "query" : { "term" : { "name" : "beer" } } , "size" : 1 } 

Мне бы очень хотелось, чтобы это было сделано, но, опять же, это было предпринято в прошлом и встретило небольшой интерес.

Спаси метод

Вместо try / catch в PHP, Ruby имеет begin / rescue . Как они работают, они практически идентичны, а PHP 5.6 даже finally соответствует требованиям Ruby.

Как PHP, так и Ruby могут в любой момент восстановиться после исключения, следуя их ключевым словам try или begin , но Ruby может сделать что-то действительно умное: вы можете пропустить метод begin и спасти непосредственно из тела функции / метода:

Рубин

 def create ( params ) do_something_complicated ( params ) true rescue SomeException false end 

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

В PHP это не работает, но эта функция может выглядеть примерно так, если она реализована:

 function create ( $params ) { do_something_complicated ( $params ) ; return true ; } catch ( SomeException $e ) { return false ; } 

Хотя это может и не быть крайне важным, но есть множество приятных маленьких штрихов, которые заставляют Руби чувствовать, что он хочет помочь вам.

Повторные попытки

Несколько месяцев назад я заметил действительно полезную функцию, которую я никогда раньше не замечал, ключевое слово retry :

 begin SomeModel . find_or_create_by ( user : user ) rescue ActiveRecord : :RecordNotUnique retry end 

В этом примере условие гонки вспыхивает, потому что find_or_create_by не является атомарным (ORM выполняет SELECT а затем INSERT ), что, если вам действительно не повезло, может привести к созданию записи другим процессом после SELECT но до INSERT .

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

Сосредоточьтесь на том, насколько раздражающим было бы взять один кусок кода и попробовать его снова. В Ruby вы можете сделать это:

 def upload_image begin obj = s3 . bucket ( 'bucket-name' ) . object ( 'key' ) obj . upload_file ( '/path/to/source/file' ) rescue AWS : :S3 : :UploadException retry end end 

PHP потребует от вас создать новую функцию / метод только для содержимого блока begin:

 function upload_image ( $path ) { $attempt = function ( ) use ( $path ) { $obj = $this - > s3 - > bucket ( 'bucket-name' ) - > object ( 'key' ) ; $obj - > upload_file ( $path ) ; } ; try { $attempt ( ) ; } catch ( AWS \ S3 \ UploadException $e ) $attempt ( ) ; } } 

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

Маленькая птичка говорит мне, что эта функция активно развивается как RFC, которая, как мы надеемся, скоро будет объявлена. Теоретически это может быть функция PHP 7.1, если процесс идет хорошо.

Последние мысли

В прошлом увлекался Ruby, я в основном просто писал Ruby как PHP. Работа с командой удивительных и чрезвычайно опытных разработчиков Ruby в течение прошлого года научила меня многим Rubyism и практикам, которые немного отличаются, и я не ненавижу это.

В этой статье рассказывается о вещах, которые я бы упустил в Ruby, если бы вернулся к работе с PHP, но это не помешало бы мне это сделать. Ненавистники PHP регулярно игнорируют прогресс, достигнутый основными участниками PHP, и хотя они могут не иметь функции X или Y, которая мне нравится в Ruby, они сделали несколько удивительных вещей для PHP 7, и PHP 7.1 выглядит полным интересных сюрпризов. ,

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

Хотя стандартная библиотека не будет исправлена ​​в ближайшее время, если бы PHP мог добавить несколько полезных функций и разбросанных синтаксических символов, таких как приведенные выше, то это было бы действительно очень приятно.

Другие языки могут вспыхнуть во всех направлениях, работая над новыми теориями, экспериментируя, делая классные вещи, которые приносят радость типам HackerNews, а затем материал, который имеет смысл для PHP, в конце концов схватывается. Мне нравится этот подход. В конце концов, PHP — грабительский пират .

Играйте с таким количеством языков, на которое у вас есть время и интерес. Ruby — хорошее начало, Golang — веселое занятие, а Elixir — причудливый, но хороший вызов, но он не чувствует необходимости перепрыгивать с одного корабля на другой. Вы можете написать в нескольких, и это поддерживает ваш мозг красивым и активным.