воскресенье, 16 января 2011 г.

Программисты не умеют программировать

Недавно вновь прогремела статья из блога "Coding Horror" "Why Can't Programmers.. Program?".
Суть статьи заключается в мысли, что среднестатистический программист не способен решить простую задачу(реализация задачи "FizzBuzz", см. далее) менее чем за 10 минут(10-15, как сказано в статье).
Ещё в 2007ом данный вопрос взбудоражил всех и вся. Все начали доказывать что автор - идиот и "АПАЗОРИЛСЯ". Однако, людям удалось доказать не только это, но и то, что программисты не умеют программировать.


Для начала, вот задача "FizzBuzz":
Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".
А вот и перевод:
Напишите программу, которая выводит на экран числа от 1 до 100. При этом вместо чисел, кратных трем, программа должна выводить слово «Fizz», а вместо чисел, кратных пяти — слово «Buzz». Если число кратно и 3, и 5, то программа должна выводить слово «FizzBuzz».
via LOR
Нужно отметить что задача очень проста, а главное - описана прямо, нет нужды самому что-то додумывать, составлять ТЗ.

Для начала, стоит поговорить о том, сколько времени займёт реализация задачи средним программистом. А действительно, сколько?
Многие люди трактуют утверждение как "ты лох, не сможешь решить задачу так быстро". Это не так. Это бред. Ты себя считаешь среднестатистическим программистом?

Если да, то у меня для тебя плохие новости: среднестатистический программист кодит на пхп и не знает что такое MVC. На западе, конечно, ситуация похожа, но значительно лучше: там(в образе среднестатистического программиста) есть и джавакодеры, и рубисты. Эти программисты могли получить овер9к 5ок в школе, получить по 10 В/О, поучаствовать в 100500 проектах(среди которых могут быть крупные), заслужить уважение у многих людей, получить награды, дипломы, сертификаты etc. Среди таких программистов есть и хорошие люди, среди них есть даже умные, но которым просто нет нужды дальше развиваться, а сами они и не хотят.
Самое важное то, что такие программисты решают определённые задачи, но если ты попросишь что-то иное, что они никогда не делали, то им придётся подумать. 10-15 минут.
А теперь ПОДУМАЙ! Такой ли ты программист? Если ты читаешь эту статью, то велик шанс что ты значительно выше.

Если нет, то у меня для тебя тоже не самые приятные новости. Ты споришь(если, конечно, споришь) с тем, что кровь красная, при этом используя в качестве доказательства то, что небо синее. Т.е. то, что ты быстро умеешь решать какую-либо задачу, не говорит о том, что её так же быстро решают такие программисты, о которых говорится в статье(среднестатистический программист, напомню).

Вообще, конечно, статья на "кодинг хоррор" не говорит о том, что именно эту задачу средний программист реализует именно за 10-15 минут. Это только пример, это только цифры из головы.
НЕ БУДЬТЕ ИДИОТАМИ!
Умейте улавливать мысль. Умейте понимать мысль. Умейте читать между строк. Не придирайтесь к таким мелочам, которые не играют роли. Это слова, никому не нравится, когда люди придираются к словам, не улавливая сути! Это бесит, выводит автора из себя.
Я это хорошо испытал на себе и не пожелаю таких вещей даже врагу.

Главная мысль, которую нужно было понять: сегодня, среднестатистический программист, который может работать рядом с каждым, каких людей больше 90% в IT-индустрии, не способен быстро реализовать простейший алгоритм и ему для этого нужно небольшое время.

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

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

В качестве источников будут использоваться 3 статьи:
На ЛОРе
На Хабре
Оригинальная статья на Coding Horrors


Первый алгоритм


Visual Basic Script:
Dim i

For i = 1 to 100
 If (i Mod 3 = 0) And (i Mod 5 = 0) Then
  WScript.Echo "FizzBuzz"
 ElseIf (i Mod 3 = 0) Then
  WScript.Echo "Fizz"
 ElseIf (i Mod 5 = 0) Then
  WScript.Echo "Buzz"
 Else
  Wscript.Echo i
 End If
Next
Как можно судить по языку, на котором написан этот код, автор являлся начинающим программистом. Стоит отметить, что сам VBS является таким языком, на котором программируют либо начинающие, либо любители MS Office, а сам язык не обладает таким уж количеством инструментов, из-за чего приходится часто заниматься такими вот задачами.
Решение не хитрое. Прямое, но хорошо выполняющее задачу. Это один из правильных вариантов.

Мой код(написание - меньше минуты), в лоб:
for i in xrange(1, 101):
   if (i % 3) == 0 and (i % 5) == 0:
      print "FizzBuzz"
   elif (i % 3) == 0:
      print "Fizz"
   elif (i % 5) == 0:
      print "Buzz"
   else:
      print i

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


Второй алгоритм


Есть и второй вариант алгоритма.
Острый глаз программиста может заметить здесь что третья фраза образуется из двух первых: "FizzBuzz". При этом, логика как-бы намекает на одну мысль, которая реализована в этом варианте:
for i in range(1,101):
    s = ''
    if (i % 3 == 0):
        s += 'Fizz'
    if (i % 5 == 0):
        s += 'Buzz'
    if not s:
        s += str(i)
    print s

Вариант быстрее(в теории), короче. Но у него есть, как минимум, один большой минус - теряется логика, читаемость и очевидность. Комментарий придётся писать, но если использовать первый алгоритм, то можно обойтись и без него.


А вот дальше идут более интересные примеры.

Руби. Первый алгоритм.
1.upto(100) { |n| puts n % 3 == 0 ? n % 5 == 0 ? "fizzbuzz" : "buzz" : n % 5 == 0 ? "fizz" : n }

Стоит отметить что выполняя простую задачу, все стремятся выполнить её в одну строчку или просто как можно более компактно. Не думайте о объёме кода, думайте о алгоритме! Отбросьте лямбды, замыкания, оптимизацию, инструменты. Думайте не инструментами, а просто логикой!
Сокращая код, реализующий плохой алгоритм, вы сокращаете буквы, когда можно слова!
Вот два похожих примера:
for i in ("FizzBuzz" if not x % 15 else "Fizz" if not x % 3 else "Buzz" if not x % 5 else x for x in xrange(1,101)): print i

map( print, ( "FizzBuzz" if not x % 15 else "Fizz" if not x % 3 else "Buzz" if not x % 5 else x for x in xrange(1,101)) )

Заметьте математическую оптимизацию в виде замены двойного условия на одно:
if not x % 15

Интересный вариант, но не имеет права на жизнь.
for i in range(1,101): print {0:"FizzBuzz", 3:"Fizz", 6:"Fizz", 9:"Fizz", 12:"Fizz" , 5:"Buzz", 10:"Buzz"}.get(i%15, i)

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

Вот здесь такая подборочка.

Всё это адов страх и ужас. Более всего пугает тот факт, что люди часто пишут такой код с самого начала, так и понимая задачу. Они тратят кучу времени на этот код, но в результате получают говно, которое не только трудно читать, теряет алгоритм в коде, не даёт понять задачу, но ещё и оказывается затратнее прямого варианта. Зачем?

Более всего интересен тот факт, что многие просто не могут понять задачу, упускают какие-либо моменты, трактуют свои правила.
for (int i = 1; i 101; i++)
{
 if ((i % 3) == 0) Console.Write("Fizz");
 if ((i % 5) == 0) Console.Write("Buzz");
 Console.WriteLine();
}

for num in range(1, 101):
 if not num % 3:
  print "bizz"
 elif not num % 5:
  print "buzz"
 else:
  print num

#CF (SCRIPT) version
for (i = 1; i LTE 100; i = i + 1) {
if ((j MOD 3) AND (j MOD 5)) {
WriteOutput(i);
} else {
if (NOT j MOD 3) {WriteOutput('FIZZ');} 
if (NOT j MOD 5) {WriteOutput('BUZZ');}
}
WriteOutput(chr(13) chr(10));
}

for (int i = 0; i < 100; i++) {
 bool byThree = (!(i % 3));
 bool byFive = (!(i % 6));
 if (byThree) std::cout << "Fizz";
 if (byFive) std::cout << "Buzz";
 if (!byThree && !byFive) std::cout << i;
 std::cout << std::endl;
}

#include 
#include 
int main(int argc, char * argv[])
{
   int i = 0;
   for (i = 0; i < 100; i++) {
      if (i%3 == 0)
         printf("Fizz\n");
      else if (i%5 == 0)
         printf("Buzz\n");
      else if (i%3 == 0 && i%5 == 0)
         printf("FizzBuzz\n");
      else
         printf("%d\n", i);
   }
   return EXIT_SUCCESS;
}
Etc. Много разного кода, который по невнимательности написан таким, что не может выполнять задачу правильно. А про то, что люди забывают использовать для сравнения <= вместо <, при этом используют число 100, в результате чего выполняется цикл до 99, а не 100... В последнем сырце никогда не будет напечатано FizzBuzz. Почему? Подумайте. Ответ прост.

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

(defun fizzbuzz (n)
(when ( n 0) 
(fizzbuzz (- n 1))
(format t "~a~%" 
(if (= (mod n 3) 0)
(if (= (mod n 5) 0) "FizzBuzz" "Fizz")
(if (= (mod n 5) 0) "Buzz" n)))))
(progn (fizzbuzz 100) (values))

def fizzbuzz(n):
 if n:
 if n % 15 == 0: return fizzbuzz(n-1) + 'fizzbuzz ';
 elif n % 5 == 0: return fizzbuzz(n-1) + 'buzz ';
 elif n % 3 == 0: return fizzbuzz(n-1) + 'fizz ';
 else : return fizzbuzz(n-1) + ('%d ' % n)
 return ''
fizzbuzz(100)

for (x=1;x101;x++)
{
sprintf(temp,"%s",x%3 ? "":"fizz");
sprintf(tem1,"%s",x%5 ? "":"buzz");
strcat(temp,tem1);
if (!temp[0])
sprintf(temp,"%d",x);
printf("%s\n",temp);
}

#include 
int main(int argc, char *argv[argc])
{
   {
   char const *output[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};
   for(size_t i = 1, check = 0; i <= 100; ++i, check = 0)
   {
      if(0 == i%3)check |=1;
      if(0 == i%5)check |=2;
      printf(output[check], i);
   }
   }
   return getch();
}

toString :: Int -> String
toString n | n `mod` 15 == 0 = "FizzBuzz"
           | n `mod`  3 == 0 = "Fizz"
           | n `mod`  5 == 0 = "Buzz"
           | True            = show n
solve :: [Int] -> IO ()
solve [] = return ()
solve (x:xs) = do putStrLn (toString x)
                  solve xs
main :: IO ()
main = solve [1 .. 100]

program main
    character*8 str
do i=1,100
   write(str(1:8),'(i8.1)') i
   If (mod(i,3).eq.0) then
      write(str(1:8),'(A8)') "Fizz    "
   endif
   If (mod(i,5).eq.0) then
      write(str(1:8),'(A8)') "Buzz    "
   endif
   If (mod(i,15).eq.0) then
      write(str(1:8),'(A8)') "FizzBuzz"
   endif
   write(*,*) str
enddo

100.times do |i|
  num=i+1
  [ (num % 3).zero? ? "Fizz" : nil,
    (num % 5).zero? ? "Buzz" : nil,
    ((num % 3).zero? or (num % 5).zero?) ? nil : num,
    ","
    ].compact.join.display  
end

% 2min
-module(fizzbuzz).
-compile(export_all).
f(N) when (N rem 15) == 0 -> io:format("FizzBuzz~n", []), f(N+1);
f(N) when (N rem 5) == 0 -> io:format("Buzz~n", []), f(N+1);
f(N) when (N rem 3) == 0 -> io:format("Fizz~n", []), f(N+1);
f(101) -> ok;
f(N) -> io:format("~p~n", [N]), f(N+1).
start() -> f(1).

let divs = [(3, "Fizz"); (5, "Buzz")]
let prefix i = 
    divs 
    |> Seq.filter (fun (d, _) -> (i % d) = 0) 
    |> Seq.map snd 
    |> String.concat ""
let convert i = 
    let special = prefix i
    if special = "" then
        string i
    else
        special
{1..100} 
|> Seq.map convert 
|> Seq.iter (fun x -> printfn "%A" x)
Etc. Да, мне страшно за умных людей. Я не просто так говорю что на ЛОРе, например, сидят профессионалы. Большинство реально профессионалы своего дела(тем более в сравнении с "среднестатистическим программистом"). Увы, мало кто способен выбирать верный путь, инструмент. Это жуть как пугает.

Часто люди становятся рабами плохих технологий. Среди них есть и авторы предыдущих сырцов. Это очень, очень обидно.

#php
for($i=0;$i++<100;)
{
$f=$i%3?'':'Fizz';
$b=$i%5?'':'Buzz';
echo$f.$b?$f.$b:$i,',';
}

(loop for i from 1 to 100 do
  (print (cond ((zerop (mod i (* 3 5))) "FizzBuzz")
               ((zerop (mod i 3)) "Fizz")
               ((zerop (mod i 5)) "Buzz")
          (t i))))

for (1..100) {
        my $t = '';
        $t = 'Fizz' unless $_ % 3;
        $t .= 'Buzz' unless $_ % 5;
        $t ||= $_;
        print "$t\n";
}

Процедура ПриНачалеРаботыСистемы()
  Для Сч = 1 По 100 Цикл
    Если Сч % 15 = 0 Тогда
      Сообщить("FizzBuzz")
    ИначеЕсли Сч % 3 = 0 Тогда
      Сообщить("Fizz")
    ИначеЕсли Сч % 3 = 0 Тогда
      Сообщить("Buzz")
    Иначе
      Сообщить(Сч)
    КонецЕсли;
 КонецЦикла;
КонецПроцедуры


Console.WriteLine(
                string.Join(
                    Environment.NewLine,
                    Enumerable.Range(1, 100)
                        .Select(num =>
                            num % 5 == 0 && num % 3 == 0 ? "FizzBuzz" :
                            num % 3 == 0 ? "Fizz" :
                            num % 5 == 0 ? "Buzz" :
                            Convert.ToString(num)
                        )
                )
 );

#SWI-Prolog
fizzBuzz(100).
fizzBuzz(N) :-
 I3 is N mod 3,
 I5 is N mod 5,
 print( I3, I5, N ), nl,
 N2 is N + 1,
 fizzBuzz(N2).
print( 0, 0, _ ) :-
 write( 'FizzBuzz' ).
print( 0, _, _ ) :-
 write( 'Fizz' ).
print( _, 0, _ ) :-
 write( 'Buzz' ).
print( _, _, N ) :-
 write(N).

#gprolog
fizzlist(0, [], M).
fizzlist(N, [H|T], M) :- N > 0, N1 is N - 1, M1 is M - N + 1, fizzbuzz(M1, H), fizzlist(N1, T, M).
fizzlist(N, L) :- fizzlist(N, L, N).
fizzbuzz(N, N) :- N mod 3 > 0, N mod 5 > 0.
fizzbuzz(N, 'Fizz') :- 0 is N mod 3, N mod 5 > 0.
fizzbuzz(N, 'Buzz') :- 0 is N mod 5, N mod 3 > 0.
fizzbuzz(N, 'FizzBuzz') :- 0 is N mod 3, 0 is N mod 5.

#mercury
:- module fizzbuzz.
:- interface.
:- import_module io.
:- pred main(io, io).
:- mode main(di, uo) is det.
:- implementation.
:- import_module int.
main --> fizzbuzz(1).
fizzbuzz(N) --> 
 ( {N =< 100} ->
  print_fizzbuzz(N),
  print(" "),
  fizzbuzz(N + 1)
 ; []
 ).
print_fizzbuzz(N) --> 
 ( {N mod 3 = 0} ->
  ( {N mod 5 = 0} ->
   print("fizzbuzz")
  ; print("fizz")
  )
 ; {N mod 5 = 0} ->
  print("buzz")
 ; print(N)
 ).
Etc.  А ещё есть много любителей преждевременной оптимизации. 

#include 
int main()
{
   int i;
   for (i=1;i<=100;i++){
      if (!(i % 3)){
         if (!(i % 5)) printf("Fizzbuzz\n");
         else printf("Fizz\n");
      } else if (!(i % 5)) printf("Buzz\n");
      else printf("%d\n",i);
   }
   return 0
}

for ($i = 1; $i <= 30; $i++)
{
    if (($i % 3 != 0) AND ($i % 5 != 0)) echo $i;
    else
    {
        if ($i % 3 == 0) echo 'Fizz';
        if ($i % 5 == 0) echo 'Buzz';
    }
    echo '
';
}

for i := 1 to 100 do
if i mod 3 = 0 then
if i mod 5 <> 0 then
print('Fizz')
else
print('FizzBuzz')
else
if i mod 5 <> 0 then
print(i)
else
print('Buzz');
Etc. Здесь даже нечего добавить.

А ещё есть огромное число либо новичков, либо не опытных кодеров. Много кода, который использовал дополнительные переменные, флаги, иной бред.

bool printint;
for (int i = 1; i = 100; i++)
{
printint = true;
if (i % 3 == 0) { Console.Write("Fizz"); printint = false; }
if (i % 5 == 0) { Console.Write("Buzz"); printint = false; }
if (printint) Console.Write(i.ToString());
Console.WriteLine();
}

public class FizzBuzz
{
public static void main(String [] args)
{
int k;
for(int i=1; i=100; i++)
{
k = 0;
if(i%3==0)
{
k = 1;
}
if(i%5==0)
{
k = k + 2;
}
switch(k)
{
case 1:
System.out.println("Fizz");
break;
case 2:
System.out.println("Buzz");
break;
case 3:
System.out.println("FizzBuzz");
break;
default:
System.out.println(i);
}
}
}
}

#include stdio.h
int main(void)
{
int i,j=0;
for(i=0;i=100;i++)
{
if((i%3)==0)
{
printf("Fizz");
j=1;
}
if((i%5)==0)
{
printf("Buzz");
j=1;
}
if(!j)
{
printf("%d",i);
}
printf("\n");
j=0;
}
}

#include 
int main() {
  for (int i=1; i<=100; i++) {
    int a=i%3, b=i%5;
    if (a)
      if (b) printf("%d\n");
      else printf("Buzz\n");
    else
      if (b) printf("Fizz\n");
      else printf("FizzBuzz\n");
  }
}
Но все учились, это нормально. Стоит отметить что даже такие люди генерили решение в пределах 10 минут.

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

1.upto(100) do |i|
out = nil
out = out.to_s + 'Fizz' if i % 3 == 0
out = out.to_s + 'Buzz' if i % 5 == 0
puts out || i
end

#lua is nice!
for i=1,100 do 
fizz= (i%3==0) and "Fizz" or ""
buzz= (i%5==0) and "Buzz" or ""
print(i,fizz..buzz)
end

for (var i = 1; i <= 100; i ++) {
var x = '';
if (i % 3 == 0) x += 'Fizz';
if (i % 5 == 0) x += 'Buzz';
console.log(x || i);
}
В первом коде интересна идея применения "или" для определения что нужно напечатать: цифру или слово.
Во втором(луа) офигенна идея слияния строк.
В третьем используется такая же идея, как и в первом.


Заключение


Это ужас, ребята. Из всего кода, который был представлен людьми, около 2% является кодом, который можно понять только взглянув на него.
Очень, очень много кода, который написан используя ужасный стиль. Ужасный! Так нельзя.
Хорошо было показано то, что люди редко выбирают нужны инструмент для нужной задачи. Люди становятся рабами своих инструментов и просто не хотят верить что есть что-то лучше.
Программисты выбирают красоту семантики, вместо красоты синтаксиса.
Используют преждевременную оптимизацию, затягивая реализацию решения.
Используют ресурсы не думая о них.
Думают "в лоб".

В итоге, я точно могу сказать что, на собеседовании у меня, около 80% авторов кода просто отпали бы. Ещё 15% я бы сохранил в списке, но при появлении хоть одного из оставшихся 5%, я бы их всех отсеял или просто оставил для будущего.
Больше всего пугает тот факт, что среди авторов кода только около 3% людей являются представителями более или менее близкого к среднестатистическому программисту уровня.

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

Комментариев нет:

Отправить комментарий