19 март 2016 г.

CODE TIPS #4 - Именуване на идентификатори (aka "Спрете с 'br' и подобни")

Всеки знае, че има значение как именуваме променливите си когато пишем код. Едно просто правило - кодът трябва да е лесно четим и разбираем, защото в реалността големите проекти се разработват от екипи хора и шансът да сме единствените "свидетели" на гениалния си код е минимален. Всеки знае това... нали?

Чудех се как точно да демонстрирам някои лоши практики в именуването на идентификатори (именуване на променливи, класове, функции).  И ето че открих чудесен пример - една програма, която беше пример от учебник, който целеше да ми покаже основите на обектно-ориентираното програмиране. Страшното беше, че дори и в неграмотно състояние виждах, че това е просто бъркотия!


Просто гледайте.

  1. #include <iostream>
  2. using namespace std;
  3. class Student {
  4. public:
  5.     char Ime[11];
  6.     short Ocenki[10];
  7.     float Uspeh;
  8.     void SrUspeh();
  9.     // ...
  10. };
  11. void Student::SrUspeh()
  12. {
  13.     float s = 0;
  14.     for (int i = 0; i<10; i++) s += Ocenki[i];
  15.     Uspeh = s / 10;
  16. }
  17. class newStud {
  18.     // ...
  19. };
  20. void main()
  21. {
  22.     char KodOp; int BrLica = 0; Student Grupa[10]; newStud newGrupa[10];
  23.     do {
  24.         cin >> KodOp;
  25.         // ...
  26.     } while (KodOp != '0');
  27. }

Нека игнорираме:
- използването на именното пространство std
public нивото на достъп на член-данните
- използването на char масив вместо std::string
постинкрементацията на брояча във for цикъла
- декларирането на променливи от различен тип на 1 ред
- факта, че main() е от тип void, а не int, както е по конвенция

Нека забравим всичко и това и се съсредоточим само върху имената на идентификаторите.

Бих казал, че единствено името на класа Student си е OK, но това, предполагам, е абсолютна случайност.

1. Не използвайте български имена!


BAD
  1. char Ime[11];
  2. short Ocenki[10];
  3. float Uspeh;

GOOD
  1. char name[11];
  2. short grades[10];
  3. float average;

Ако бяхте от друга страна и не знаехте български език - каква щеше да е реакцията ви когато ви сервират първия вариант? Просто използвайте имена на английски. Английският е единственият универсален език в света на програмирането.

2. Не съкращавайте имената!


BAD
  1. char KodOp; 
  2. void SrUspeh();
  3. int BrLica = 0;
  4. float s = 0;

GOOD
  1. char operationCode; 
  2. void getAverage();
  3. int numberOfStudents = 0;
  4. float sum = 0;

Няма нужда от съкращения в повечето случаи. По-ценно е кодът да е лесно разбираем, отколкото да е кратък. Постоянно виждам в примери променливи с имена по една буква. Понякога това е допустимо - ако искаме да илюстрираме някаква концепция от програмирането или пък да приложим някаква математическа формула, но в повечето случаи няма нужда от това. 

Също така бих казал че изключение могат да направят например променливите за брояч във for цикъл, които имат малък обхват и няма да затруднят съществено четимостта на кода, тъй като съществуват единствено в цикъла.

3. Спазвайте конвенциите и бъдете постоянни!


Първоначално мислех да напиша за това, че обикновено класовете започват с главна буква, но проблемът се свежда до нещо по-общо - просто спазвайте конвенциите на езика, с който програмирате!

За всеки език е различно и задължение на програмиста да се информира за конкретните конвенции.

Още по-важно - бъдете постоянни. Ако ще пишете camelCase имена на променливи - добре, но се опитвайте да използвате този стил за всички променливи.

---

Това май са най-важните правила, които научих до този момент относно именуването... а сега отивам да бомбардирам всеки, който все още декларира променлива с име "br".

17 март 2016 г.

CODE TIPS #3 - Преинкрементирайте брояча във for цикли! (++C)

Когато започнах да пиша код на C++ ми отне известно време да схвана идеята зад for цикъла. Най-странната част беше това i++ накрая. В последствие разбрах, че просто означава "увеличи стойността на променливата i с 1". Супер! Но после открих и ++i , което прави същото, но с една съществена разлика.

Барт инкрементира правилно!


Ако имаме декларирана целочислена променлива i, инициализирана със стойност 0 и следния код:

  1. while (< 5) {
  2. std::cout << i++ << " ";
  3. }

Изходът ще е: 0 1 2 3 4
Ето какво се случва - извеждаме стойността на i и чак след това стойността й се увеличава с 1.

Но пък от друга страна, ако имаме този код вместо първия:

  1. while (< 5) {
  2. std::cout << ++i << " ";
  3. }

Изходът ще е: 1 2 3 4 5

Обяснението е просто - първо стойността на i се увеличава с 1 и след това я извеждаме на конзолата.

Но имайки предвид това как работи for цикъла, пробваме и се убеждаваме, че това няма значение, защото обновяването на брояча се случва след всяко завъртане на цикъла:

  1. // Output: 0 1 2 3 4
  2. for (int i = 0; i < 5; i++) {
  3.     std::cout << i << " ";
  4. }
  5. // Output: 0 1 2 3 4
  6. for (int i = 0; i < 5; ++i) {
  7.     std::cout << i << " ";
  8. }

Значи няма разлика, нали? Е, оказва се, че има!

При случая i++ се създава временна променлива, в която се запаметява сегашната стойност на i, стойността на временната променлива се увеличава с 1 и тази стойност се дава за нова стойност на i. При случая ++i директно се обновява стойността на i, без нуждата от първата инструкция. Тази оптимизация може да се приложи автоматично и в двата случая за примитивни типове, но не и когато използваме итератори например.

Разликата е малка и понякога несъществена, но в по-сложни програми преинкрементирането просто е добър навик.


11 март 2016 г.

CODE TIPS #2 - Stop "using namespace std;" (C++)

Много често виждам в програми/примери за C++ използването на именното пространство "std", което представлява стандартната библиотека от функции на C++.

Оказва се, че този код не е част от добрата практика:

BAD
  1. #include <iostream>
  2. using namespace std;    // avoid this

  3. int main() {
  4.     cout << "I like pickles." << endl;
  5.     return 0;
  6. }


Защо? Простото обяснение е, че могат да се появят конфликти когато използваме други библиотеки, които потенциално могат да имат идентични имена като тези на функциите (и не само) в стандартната библиотека.


Например нека имаме 2 библиотеки с имена MyLib и OtherLib.

Да речем в MyLib имаме функцията myFunction(), а в OtherLib функцията otherFunction(). Всичко е OK, спокойно можем да импортираме тези функции и да ги извикваме безпроблемно.

Но един ден се налага да обновим първата библиотека и сега сме с MyLib 2.0, която за нещастие сега също има функция, която се казва otherFunction(). Лошо! Сега, когато извикаме otherFunction() в кода си, не е ясно кой код точно ще се изпълни. Може да ни се размине ако функциите са с различни параметри, но наистина не си струва рискът.

Решението e много просто и има потенциал да ни спести много главоболия. Ако се върнем към първоначалния пример със "std", единствената промяна, която е нужна, е да назовем името на съответното именно пространство. Тоест:

GOOD
  1. #include <iostream>
  2. int main() {
  3.     std::cout << "I like pickles." << std::endl;
  4.     return 0;
  5. }


"Но това е толкова досадно, всеки път трябва да назовавам именното пространство!" - няма проблем, защото има и алтернатива:

ALSO GOOD
  1. #include <iostream>
  2. using std::cout;
  3. using std::endl;

  1. int main() {
  2.     cout << "I like pickles." << endl;
  3.     return 0;
  4. }

Аналогично може да приложите горния код за std::string или структури като std::vector, std::list и т.н. Изключително дребна промяна, която не коства нищо, а носи огромна полза.

9 март 2016 г.

Алгоритъм за решаване на задачи по програмиране

"Въведение в програмирането със C#" на Светлин Наков е първата книга по програмиране, която завърших. Пълна е с полезна информация и наистина я препоръчвам на всеки, който има интерес в сферата или тепърва започва (макар че смятам, че дори и напреднали програмисти ще имат какво да научат, просто защото някои неща се забравят). Напълно безплатна е, така че се залавяйте!

Въпреки всичката полезна информация представена в книгата, най-много ми хареса главата за решаване на задачи по програмиране. В нея бяха представени различни идеи, които улесняват решаването на по-сложни задачи и подобряват качеството на самото решение.



Аз извлякох тези идеи и ги подредих в малко по-синтезиран вид, наподобяващ алгоритъм, но не баш. Общо взето, това което научих е, че решението на една задача се свежда до измисляне на добра идея и имплементацията на тази идея (под формата на код, в контекста на програмирането). Ето как изглежда като цяло:

1. Идея:
  - Разбийте задачата на подзадачи
  - Генерирайте идеи
  - Тествайте идеите
  - При проблем измислете нова идея

2. Имплементация:
  - Подберете структурите от данни
  - Помислете за ефективността
  - Пишете стъпка по стъпка
  - Тествайте след всяка стъпка

BROTIPS:
- Използвайте лист и химикал!
- Търсете в Google!

Толкова прост метод, но ефективен! Не е нищо ново като информация, но се учудих колко приложим е този подход не само в програмирането, но и във всичко останало. А сега отивам да тествам...

7 март 2016 г.

CODE TIPS #1 - Контра-интуитивната индентация и скобите в LISP

OK, наскоро започнах да изучавам функционален език за програмиране (LISP) и ми направи впечатление следното нещо:

Толкова. Много. Скоби.


Credits: XKCD


Зачудих се как е възможно подобен код да е четим за когото и да било. Вече очевидно никой не се интересува от това... :(

Първото ми предположение беше да подредя кода както бих го подредил в стил "всеки друг език за програмиране":

BAD
  1. ;; Well, ain't that cute...
  2. (defun f (x)
  3.     (cond
  4.         ((= x 1337) x)
  5.         (nil)
  6.     )
  7. ) ; ... but it's WRONG.

Оказа се, че конвенцията не само е против излишните празни места, но дори редове само с по една затваряща скоба на тях се считат за лоша практика. Стига де!

Имайки това предвид, за по-удачен вариант се смята следната версия:

GOOD
  1. (defun f (x)
  2.   (cond ((= x 1337) x)
  3.         (nil)))

Основната разлика:
- по 2 интервала на нов ред
- аргументите се подреждат един под друг
- затварящите скоби не се изолират на цял ред

Индентацията добре, но скобите защо така?? Обяснението, което намерих, е че опитните Lisp програмисти не обръщат внимание на броя на скобите, а се съсредоточават върху това, което се случва между тях. И втората причина е, че просто се цели минимизирането на редове код (което според мен е по-рационалната причина).

Странно, но ще свикна...

6 март 2016 г.

КАК ДА СТАНЕМ ДОБРИ ВЪВ ВСИЧКО

Кратък отговор: не можем.
Дълъг отговор: можем, но на цената на това да не сме толкова добри в други неща.

Лично аз не съм намерил начин да съм на повече от малко над средно ниво във всичко, което опитвам, ако целта ми е балансиран живот, който включва неща като финансова стабилност, добро здраве, добри взаимоотношения с околните и т.н.

Въпреки това можем да станем приемливо добри в доста неща.

Според мен съществуват общо взето 2 стратегии за "успех":
- да станем НАЙ-ДОБРИТЕ в 1 нещо (и само 1)
- да сме "OK" в доста неща

Просто няма как Майкъл Джордан да е най-добрият баскетболист и най-добрия пианист едновременно. Няма как Моцарт да е велик композитор и едновременно спортист на световно ниво.

Енергията ни е ограничена и е наша отговорност да намерим правилния начин да я използваме (според личните ни цели и възгледи за "успех" в живота).

И двете стратегии си имат плюсове и минуси. На пръв поглед по-привлекателната идея сякаш е да сме "най-добрите", но помислете какво би ни струвало това.

Да речем че искам да стана най-добрия програмист в света. И по цял ден правя само това - програмирам, програмирам, програмирам. В резултат на това, действително ставам изключителен програмист, защото съм вложил толкова време и усилия, но в същото време съм забравил за здравето си и вече 10 лицеви опори се превръщат в предизвикателство. Освен това съм забравил всякакви приятелски връзки и се чувствам самотен. Хубавото е, че съм на световно ниво и вероятно ще се движа с лекота в сферата, в която искам да се развивам (било то програмиране или каквото и да било). Но колко неща трябваше да жертвам, за да стигна това ниво?

От друга страна, мога да съм "OK" (на средно или малко над средно ниво) в повече неща. И така, сега не съм най-добрия програмист. В най-добрия случай съм малко над посредствен програмист, който не е ходил на стотици олимпиади, няма медали от състезания или гениални постижения, но пък обича това, което прави, склонен е да се учи от грешките си и винаги търси развитие. Освен това съм в добро здраве и не съм заличил идеята за социален живот. Но пък не съм нищо специално откъм "skills"... така че и тази стратегия не е перфектна.

Както и да е, нека минем по същество...

КАК ДА СТАНЕМ ДОБРИ В НЕЩО?


Забелязах, че има "pattern" (повтарящ се модел) всеки път когато искам да стана по-добър в нещо.

Обикновено първото нещо, което се случва, е нещо лошо, което ми напомня "пич, не си добър в това и ще имаш проблеми ако не промениш сегашното си положение". Помня, че точно това се случи преди да започна да спортувам street workout по-активно. Бях на много ниско ниво (не можех да се набера нито 1 път) и ми беше много тежко отвътре като виждах как връстниците ми с лекота си правиха силови, коремни и т.н.

Второто нещо или втората "фаза" е тази, в която се вманиачавам в търсенето на информация по въпроса. Започвам да търся ресурси, истории на хора били в моето положение (хора, които са успeли въпреки това), мотивация и конкретни стъпки, които мога да следвам, за да стана по-добър.

Третата фаза е най-тежката - имплементацията. Иначе казано - да вкараш плана в действие. Обикновено в този етап имам ниско самочувствие, защото за дълъг период от време се очертава да съм МНОГО ЗЛЕ в това, което правя. Съмнения от рода на "струва ли си това", "на прав път ли съм" и т.н. постоянно циркулират из главата ми. В началото прогресът ми е минимален и много пъти искам да се откажа. Помня, че точно това се случи когато започнах да програмирам - бях отчаян и не знаех какво да правя, защото дори най-елементарните задачи ми се струваха невъзможни, не схващах логиката и бях доста изплашен.

Ако стигна до следващата фаза - нещата стават по-добре. Вече имам забележим прогрес и дори не ми прави впечатление, защото имплементацията вече е част от ежедневието ми (свикнал съм да правя нещото, в което искам да стана добър). Докато се усетя - вече съм сравнително добър в конкретното нещо. От там зависи само от мен дали желая да стана още по-добър в това нещо или просто да "поддържам" сегашното си ниво, но като цяло е значително по-лесно.

Това е процесът, който се повтаря всеки път. Ако трябва да го опиша с графика - нещо от този род е:

Credits: kratosguide.com


ЗАПОМНЕТЕ ТЕЗИ 3 НЕЩА


За да формулирам процеса в по-достъпен вид, ето какво се иска от вас, за да стигнете от точка A (хипер зле) до Б (not bad).

1. ЗАОБИКОЛЕТЕ СЕ ОТ ТОВА, В КОЕТО ИСКАТЕ ДА СТАНЕТЕ ДОБРИ

Това включва:
- намерете техническа информация ("how to conquer the world" в Google)
- четете книги / гледайте клипове, които ви информират и вдъхновяват
- намерете хора, които са минали по този път и ще ви вдъхновяват/окуражават
- ВСИЧКО друго, което просто ви напомня "това е постижимо и си струва"

2. ПРЕВЪРНЕТЕ ГО В НАВИК.

Казват, че ако вършите нещо всеки ден за около 66 дни - то се превръща в навик, т.е. се автоматизира и започвате да го правите без да се замисляте. Според мен зависи. Някои навици се формират за по-малко време от това, докато други изискват повече повторение. От вас се иска само едно нещо - упражнявайте се ВСЕКИ ден. Не е важно колко време ще отнеме. Насладата е в пътешествието, а не дестинацията (не се сдържах да използвам един cheesy цитат).

3. В НАЧАЛОТО НЕ ОЧАКВАЙТЕ РЕЗУЛТАТИ

В началото ще сте зле. Няма друг начин. От изключително значение е да нямате големи очаквания, защото това ще ви накара да се откажете. Просто действайте всеки ден и ВЯРВАЙТЕ в процеса и във факта, че ще станете по-добри с времето. Аз лично много съм патил от игнорирането на тази част. Големите очаквания ви карат да се съмнявате дали сте на прав път и дали си струва да влагате толкова усилия. Просто НЕ го правете (trust me)!

---

Това е! А сега действайте!