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

Этот процесс не уникален ни для меня, ни для разработки программного обеспечения. Было много разговоров о правиле 10 000 часов Малкольма Гладуэлла и его целенаправленной практике. Есть также куча книг, посвященных пути от новичка до мастера. Путешествие требует времени и много борьбы с мелочами, пока в конце концов все не начнет щелкать.

Хотя я не могу ускорить этот процесс для вас, я могу дать совет, которому, я надеюсь, вы последуете на пути от новичка к мастеру. Сам совет прост: беритесь за клавиатуру в последнюю очередь. Другими словами, когда вы сталкиваетесь с проблемой кодирования, код — это последнее, о чем вы должны думать. Код — это побочный продукт решения, а не само решение. Это может показаться немного нелогичным, особенно если вашей задачей является написание программного обеспечения, и вам будет сложно следовать этому правилу, но при достаточной практике и внимательном отношении к этому шагу вам будет легче следовать по мере продвижения по карьерной лестнице.

Чтобы проиллюстрировать эту идею, давайте посмотрим на нее в действии. Мы попробуем решить один из кодовых ката, предоставленных http://codewars.com, который представляет собой следующую задачу:

Some numbers have funny properties. For example:
89 --> 8¹ + 9² = 89 * 1
695 --> 6² + 9³ + 5⁴= 1390 = 695 * 2
46288 --> 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51
Given a positive integer n written as abcd... (a, b, c, d... being digits) and a positive integer p we want to find a positive integer k, if it exists, such as the sum of the digits of n taken to the successive powers of p is equal to k * n. In other words:
Is there an integer k such as : (a ^ p + b ^ (p+1) + c ^(p+2) + d ^ (p+3) + ...) = n * k
If it is the case we will return k, if not return -1.
Note: n, p will always be given as strictly positive integers.
dig_pow(89, 1) should return 1 since 8¹ + 9² = 89 = 89 * 1
dig_pow(92, 1) should return -1 since there is no k such as 9¹ + 2² equals 92 * k
dig_pow(695, 2) should return 2 since 6² + 9³ + 5⁴= 1390 = 695 * 2
dig_pow(46288, 3) should return 51 since 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51

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

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

  1. Даны положительное целое число n и положительное целое число p
  2. Найдите k, например k * n = (a ^ p + b ^ (p + 1) + c ^ (p + 2) ..., где a, b, and c — цифры в n.
  3. Проверить, является ли k целым числом
  4. Если k является целым числом, вернуть k, иначе вернуть -1.

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

step 1: take n and split it to the list of digits
step 2: go through the list of digits, one by one, d(i) where i is the index of the digit in the list starting at 0 from the left
step 3: raise d(i) to the power p + i and add it to the sum (sum += d(i) ^ (p + i))
step 4: determine the value of k such that k = sum / n
step 5: check if k is an integer
step 6: if k is an integer, return k. otherwise, return -1

Переход от исходного описания к приведенному выше описанию шагов очевиден постфактум, но требует усилий от того, кто к этому не привык. Следует отметить, что определение шагов все еще на простом английском языке с некоторыми математическими обозначениями. Но до этого момента мы еще не обсуждали никакого кода. Это главный вывод. Самое большое усилие, на котором должен сосредоточиться разработчик, — это способность взять некоторые требования, независимо от того, хорошо они структурированы или нет, и преобразовать их в набор понятных и воспроизводимых шагов. Последний шаг, перевод этих шагов в код, является легкой частью. Как только вы поймете, что строите, дальше будет просто преобразование английского языка в синтаксис любого используемого вами языка программирования. Гугл в этом хорош ;)

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

Шаг 1: digits = n.to_s.chars.map(&:to_i)
Шаг 2: digits.each_with_index { |d, i| }
Шаг 3: sum += d ** (p + i)
Шаг 4: k = sum / n
Шаг 5: k % 1 == 0
Шаг 6: k % 1 == 0 ? k : -1

Если вы думаете, что это слишком просто и слишком легко, так и есть. Это так, потому что мы приложили все усилия на начальном этапе перевода требований в краткие и понятные шаги. Перевод каждого шага в код очень прост. Осталось собрать все воедино:

def dig_pow n, p
  sum = 0
  n.to_s.chars.map(&:to_i).each_with_index { |d, i| 
    sum += d ** (p + i) 
  }
  k = sum / n
  k % 1 == 0 ? k : -1
end

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

Если, с другой стороны, вы начали решать эту задачу, набрав что-то вроде:

def dig_pow n, p
  (1..p).each do
    ...  
  end
end

or

def dig_pow n, p
  n >= 0 && p >= 0
  k = n**p + n**(p+1) + n**(p+2)
end

Это указывает мне на то, что вы просто гадаете, вы взяли горсть спагетти, швырнули ее в стену и надеетесь, что что-то прилипнет, не до конца понимая, что на самом деле требуется и как этого добиться. Тем не менее, многие разработчики начинают именно так. Я тоже это сделал. У всех нас есть. Мы начинаем с предположения, что понимаем проблему, и начинаем вводить код, думая, что код приведет нас к решению. Хотя на самом деле печатание должно быть последним, что мы делаем в процессе. Думать быть первым. Анализ второго. Стратегия третья…

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

Я надеюсь, что это поможет и сделает ваш путь к мастерству менее болезненным :)