Марк Уэстлинг уже ответил на ваш ближайший вопрос, но, поскольку вы упомянули, что вы новичок в Ruby, вот несколько других советов по стилю:
def subdirs
subdirs = Array.new
Обычно предпочтительно использовать буквальный синтаксис, где это возможно, поэтому наиболее распространенный способ выразить это будет subdirs = []
.
self.each do |x|
self
- неявный получатель в Ruby, так что вы можете просто отключить его. (В конце концов, это не Python.) Однако основная цель кода - это общение, поэтому, если вы верите, это лучше передает ваши намерения, оставьте это.
Говоря об общении: x
не очень коммуникативное имя, если только вы не говорите о точках в декартовой системе координат. Как насчет file
, или, если вас не устраивает представление Unix о том, что каталоги также являются файлами, более нейтральный entry
? (На самом деле, лучшим из них, вероятно, будет path
, но мы скоро увидим, как это может сбить с толку.)
Мое третье предложение на самом деле касается личных предпочтений, которые противоречат общепринятому стилю Ruby: общепринятый стиль Ruby диктует, что однострочные блоки ограничиваются {
/ }
, а многострочные блоки - do
/ end
Так же, как и вы. Мне это не нравится, потому что это произвольное различие, которое не передает никакого значения . (Помните, что такое «коммуникация»?) Итак, я на самом деле все делаю иначе: если блок имеет императивный характер и, например, меняет какое-то глобальное состояние, я использую ключевые слова (потому что блок на самом деле do
выполняет некоторую работу ) и если блок является функциональным по своей природе и просто возвращает новый объект, я предпочитаю использовать фигурные скобки (потому что они выглядят красиво математически). Так что в этом случае я бы использовал фигурные скобки.
if File.directory?(x)
Как уже объяснил Марк, вам нужно сделать что-то вроде File.directory?(File.join(path, entry))
здесь, где Dir#path
- это публичный атрибут класса Dir
, возвращающий путь, с которым Dir.new
был инициализирован.
Здесь вы также видите, почему мы не использовали path
в качестве имени параметра блока. В противном случае нам нужно было бы написать File.directory?(File.join(self.path, path))
.
subdirs.push(x)
Каноническим способом добавления элемента в массив или добавления чего-либо к чему-либо в Ruby является оператор <<
. Итак, это должно читаться как subdirs << entry
.
Array#push
- это псевдоним Array#<<
, который в основном предназначен для того, чтобы вы могли использовать Array
в качестве стека (вместе с Array#pop
).
end
end
return subdirs
Вот еще одно несоответствие между моим личным стилем и общим стилем Ruby: в Ruby, если нет явного return
, возвращаемое значение - это просто значение последнего выражения. Это означает, что вы можете опустить ключевое слово return
, и общий стиль Ruby говорит, что вы должны. Я, однако, предпочитаю использовать это подобно разделителям блоков: используйте return
для методов, которые функциональны по своей природе (потому что они на самом деле «возвращают» значение), и не return
для императивных методов (потому что их real Возвращаемое значение - не то, что следует за ключевым словом return
, а то, какие побочные эффекты они оказывают на окружающую среду). Поэтому, как и вы, я бы использовал здесь ключевое слово return
, несмотря на общий стиль.
Также принято отделять возвращаемое значение от остальной части тела метода пустой строкой. (Кстати, то же самое касается кода настройки.)
end
Итак, вот где мы сейчас находимся:
def subdirs
subdirs = []
each { |entry|
if File.directory?(File.join(path, entry))
subdirs << entry
end
}
return subdirs
end
Как видите, выражение if
действительно служит только для пропуска одной итерации цикла. Это намного лучше сообщается с помощью ключевого слова next
:
def subdirs
subdirs = []
each { |entry|
next unless File.directory?(File.join(path, entry))
subdirs << entry
}
return subdirs
end
Как видите, нам удалось удалить один уровень вложенности из структуры блока, что всегда является хорошим признаком.
Эта идиома называется «защитное предложение» и довольно популярна в функциональном программировании, где во многих языках даже встроены защитные конструкции, но она также широко используется практически во всех других языках на планете, потому что это значительно упрощает управление: идея аналогична охраннику, размещенному вне замка: вместо того, чтобы впускать в замок плохих парней (метод, процедура, функция, блок, ...), а затем мучительно пытаться отслеживать их каждое движение постоянно боясь пропустить что-то или потерять их, вы просто отправляете охранника у входа вашего замка (начало вашего метода, блока, ...), который не позволяет им начать с (который переходит в конец процедуры, рано возвращается из метода, пропускает одну итерацию цикла, ...). В Ruby для этого вы можете использовать raise
, return
, next
и break
. На других языках даже GOTO
хорошо (это один из тех редких случаев, когда GOTO
может на самом деле очистить поток управления).
Тем не менее, мы можем упростить это еще больше, распознав шаблон итератора: вы берете список (записи каталога), а затем «разбиваете» этот список на один объект (массив subdirs
). Теоретику категории это кричит "катаморфизм", хардкорному функциональному программисту "fold
", программисту функционального программного обеспечения "reduce
", программисту Smalltalk "inject:into:
" и Rubyist "Enumerable#inject
«:
def subdirs
return inject([]) { |subdirs, entry|
next subdirs unless File.directory?(File.join(path, entry))
subdirs << entry
}
end
inject
использует возвращаемое значение предыдущей итерации для заполнения следующей, поэтому мы должны вернуть subdirs
, даже если мы пропускаем итерацию, используя next subdirs
вместо простого next
( который вернет nil
, так что на следующей итерации subdirs
будет nil
, а subdirs << entry
вызовет NoMethodError
.)
(Обратите внимание, что я использовал фигурные скобки для блока, хотя блок фактически изменяет свой аргумент. Хотя я чувствую, что это все еще «функциональный» блок. YMMV.)
Последнее, что мы можем сделать, это признать, что мы просто фильтруем (или, другими словами, "выбираем") элементы массива. А в Ruby уже есть встроенный метод, который делает именно это: Enumerable#select
. Обратите внимание на удивительность в одну строку, которая, используя всю мощь Ruby, генерирует:
def subdirs
return select { |entry| File.directory?(File.join(path, entry)) }
end
Как общий совет: изучите чудеса Enumerable
. Это рабочая лошадка для программирования на Ruby, аналогичная IEnumerable<T>
в .NET, dict
s в Python, списки на функциональных языках или ассоциативные массивы в PHP и Perl.