Елена Булыгина предлагает Вам запомнить сайт «Ленусик»
Вы хотите запомнить сайт «Ленусик»?
Да Нет
×
Прогноз погоды

Основная статья: Логика

Логические ошибки. Часть 2: диагностика алгоритма

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

Главное правило проверки алгоритма — одинаковые вводные всегда должны вести к одному и тому же результату. Мы собрали несколько рекомендаций, которые помогут улучшить ваш алгоритм и созданный на его основе код.

Начните с малого и тренируйтесь «на кошках»

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

Запишите алгоритм в упрощенном виде, чтобы оценить его применимость. Оставьте минимум ветвления и возьмите только удобные для расчетов входные данные. Вводите новые требования и усложняйте конструкцию постепенно. Так вы быстрее исключите варианты, которые не позволяют решить задачу в принципе или при имеющихся ресурсах (в числе которых — и ваш уровень знаний).

Пишите «от лица компьютера». Вы придумываете программу действий для машины, а не для себя. Если пишете на языке сравнительно низкого уровня (С++), «Сохранение документа» — это взгляд программиста. В языках типа Python, где код приближен к естественной речи, такая формулировка может быть понятна машине. Учитывайте это. Привыкайте смотреть на код с точки зрения компьютера, который ждет ответа на вопрос «Что делать?». В комментариях и пояснениях используйте настоящее время или повелительное наклонение: «выводим строку», «выведи строку».

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

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

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

Делите код на самостоятельные блоки, которые можно проверить unit-тестированием. Иначе усложните себе поиск багов. Если участки кода, которые отвечают за конкретное поведение, не автономны — цепляются за внешний код и друг за друга — сложная структура и человеческий фактор доведут до ошибок. Работа тестировщика тоже усложнится. Вносите и тестируйте новый код небольшими кусочками, чтобы потом не запутаться в «простыне».

Посмотрите вебинар: «Быстро или медленно? Введение в анализ алгоритмов».

Не оставляйте бизнес-логику на потом

Для новичка это совет на будущее. Иногда заказчик тянет с проработкой бизнес-части, а программист уже пишет код и по своим прикидкам «оставляет место» под будущий функционал. Позже либо приходится все переписывать, либо итоговый продукт вызывает чувство стыда у всех причастных. Не начинайте работать без продуманной бизнес-логики: трясите тех, кто за это отвечает, и не пытайтесь сделать работу за них!

Если пока заказчик  — вы сами, срочно свяжитесь с собой и выясните детали.

Станьте следопытом кода

Отслеживайте выполнение программы с помощью инструментов отладки в вашей IDE. Создавайте контрольные точки и отражайте их выполнение в консоли. Так вы скорее найдете участок, где поведение программы начинает расходиться с ожиданиями. Например, где вместо else начинает выполняться другой блок кода. Не жалейте на это времени — без отладки его уйдет еще больше.

Когда простая слежка не приносит результатов, попробуйте старый метод под названием «Остановись, мгновенье!». Расставьте по участкам кода точки останова. На них выполнение кода прерывается, и можно экспериментировать в консоли. Проверяйте значения переменных, чтобы понять, все ли идет по плану. Затем возобновите исполнение кода до следующей точки.

Если запутались, сыграйте в настольную игру «Я — компьютер»

Правила просты:

  1. Распечатываете полный листинг своей программы.
  2. Отдельно распечатываете список переменных с начальными значениями.
  3. Берете чистый лист — это «бумажная консоль». Не смейтесь, профессиональные разработчики иногда так играют, потому что это помогает.
  4. Начинаете читать листинг с первой строки.
  5. Выполняете строку кода в голове.
  6. Фиксируете новые значения переменных на соответствующем листе.
  7. Пишете результаты в «консоль».
  8. Смотрите, ожидаем ли результат:
    1. Если нет — вы напали на след. Ищите причину несоответствия;
    2. Если да — переходите к следующей строке.
  9. Повторяете пункты 5–8 до обнаружения ошибки.
  10. Идете за компьютер и правите код.
  11. Вы победили! Салют — ламбада — пирожок.

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

Играйте с чужим кодом

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

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

Не игнорируйте математику

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

Программирование и математика пересекаются, и людям изначально гуманитарного склада не нужно этого бояться. Вы обязательно столкнётесь с разными системами счисления  — почитайте о них. Ещё более важной областью применения математики для вас станет работа над алгоритмом программы. Умение видеть за каждой задачей подходящие формулы и инструменты вырабатывается постепенно. И чем раньше вы начнете этому учиться, тем лучше. Разбирайте чужие алгоритмы, смотрите, что непонятно, ищите объяснения в интернете и в личных беседах с преподавателями, коллегами.

Многое будет зависеть от того, какое именно ПО вы будете разрабатывать. С новыми задачами появляются и новые потребности в знаниях  — программист начинает штудировать конкретные темы. Не говорите себе «стоп-слов», а поймите, что прикладная математика — это необходимо и посильно.

Начните с вебинара «Зачем программисту нужно знать математику?».


11 июл 18, 17:07
0 0
Статистика 1
Показы: 1 Охват: 0 Прочтений: 0

Логические ошибки. 7 бед начинающего программиста

Хуже багов в коде — только ошибки в логике программы и нашем мышлении. Сборка и запуск проходят гладко — до поры вы не подозреваете о проблеме. Но вдруг приложение начинает работать не так, как задумано. Обычно это происходит, если запустить код с непредвиденными входными данными. Результаты вычислений оказываются искаженными, или вы обнаруживаете дыры в безопасности. Ищете баг, но его нет — программа делает, что сказано. Остается вопрос: что вы не предусмотрели? Системная ошибка может потребовать переделать код полностью или почти с нуля. Застраховаться от таких неприятностей нельзя, но можно учиться на чужом опыте и обходить заведомо опасные ситуации.

Кунсткамера: «Почему это плохо работает?»

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

«Не туда положил», или неверный тип данных

Ошибка из разряда «Семен Семеныч!». Если вы неправильно выбрали тип переменной или он непредвиденно изменился во время работы кода, это может создать проблему. Например, программа попытается записать 64-битное значение в 32-разрядную переменную. Или число одинарной точности с плавающей запятой (float) попадает в переменную типа int (Integer).

У такой ситуации несколько вариантов развития:

  • В языках со статической типизацией (С++, Java, С#) значения переменных проверяются на этапе компиляции. Если мы явно положили значение типа long в int, получим ошибку и шанс ее исправить. Но баг всегда найдет лазейку — в С#, например, если по итогам вычислений записать 1,7 в int, это пройдет незаметно. Причем число будет округлено до 1 и результаты дальнейших вычислений с участием переменной исказятся.
  • В языках с динамической типизацией (JavaScript, Python, PHP) переменные получают значение во время работы программы. Ничто не укажет на ошибку, пока вы не столкнетесь с ней непосредственно. Неявное приведение типов в таких языках — норма жизни. Поэтому 32-битная int, если в нее поместить число с плавающей запятой, автоматически превратится во float, и никто вам об этом не скажет. Лучше еще на этапе создания алгоритма следить, чтобы у переменной не было случая самовольно обратиться «в другую веру».

Хрестоматийный пример — вещественные вычисления с целыми числами, такие как деление с остатком. Вещь настолько банальная, что в современных языках с ней просто не может что-то пойти криво… Или может.

int a = 25;
int b = 8;
float c = a/b;
Console.Write(c);
Console.Write("\n"+ c.GetType()); // Проверяем тип переменной c

Результат:

3
System.Single

На самом деле мы получили 3,125, но значение округлилось. Указание типа float для “с” не помогло сохранить точность. Компилятор сначала выполнил деление и округлил результат, а потом уже задумался о типе… и выбрал Single. Это тип, который хранит числа с плавающей запятой одиночной точности в диапазоне от -3,402823E38 до 3,402823E38. Типы float и Single — оба 32-разрядные. C# в таких случаях допускает неявное преобразование, так что компилятор не заметил подвоха.

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

float c = (float)a/b; 

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

Проверяйте типы в вычислениях при вводе данных пользователем и при чтении из файла! Разберитесь,что такое статическая и динамическая типизация, явная и неявная. Не жалейте времени, чтобы узнать, как работает с типами ваш ЯП.

Пример округления при делении int на int с остатком был актуален и для Python 2. В Python 3 это работает иначе:

a = 25
b = 8
c = a/b
print(c)
"""Проверяем тип переменной"""
print(type(c) == float)

Результат:

3.125
True

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

Вебинары GB: «Все, что вы должны знать о типах данных»  «Типы данных в PHP», «Типы данных языка C#», «Базовые типы данных, ветвление и циклы языка Java» .

Что читать: «Ликбез по типизации в языках программирования», «Статическая и динамическая типизация», «Исследование внутренних механизмов приведения типов в JavaScript», «Приведение и преобразование типов C#», «Опциональные типы данных в языке Swift».

«На волю, всех на волю!» — высвобождение ресурсов

Если вы используете язык программирования с автоматической сборкой мусора, не забывайте присматривать за высвобождением ресурсов. Большую часть времени все работает хорошо, но иногда сборщик начинает приносить больше проблем, чем пользы. Кто работал с Java, знает, что удаление отработанных данных может затянуться на неопределенный срок. Виртуальная машина ищет, какие объекты больше не нужны — в коде на них нет ссылок. Объекты попадают в очередь на уничтожение, но она может не дойти до них никогда. Хранение лишних ресурсов грузит память и создает уязвимость: среди отправленных на свалку объектов могут оставаться конфиденциальные данные.

Поэтому будьте внимательны. Можете принудительно освобождать ресурсы с помощью конструкций типа try-with-resources и try-finally. Так вы убедитесь, что все лишнее будет стерто при любом раскладе. Отпускать ресурсы на волю лучше «в естественную среду обитания» — в том же коде, где их захватили.

А еще привыкайте закрывать открытые файлы и сессии, как скобки в коде.

Что читать: «Правильно освобождаем ресурсы в Java», «Очистка неуправляемых ресурсов» (C#), «Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними».

Неслучайные случайности

На основе случайных чисел можно создавать уникальные ID для объектов и сессий, генерировать пароли, вносить разнообразие в геймплей или общение пользователя с программой. Главное, чтобы случайность не стала слишком предсказуемой. Увы, в языках программирования используют генераторы не случайных, а псевдослучайных чисел (ГПСЧ). Получить действительно случайное число не так просто. Для этого нужны внешние сигналы, последовательность которых для компьютера всегда неожиданна. Обычно в таких случаях используют аппаратуру, которая фиксирует хаотически меняющиеся параметры физического процесса. Основой рандомизации могут быть движущиеся частицы, белые шумы, микродвижения (смещение координат курсора мыши) или текущее число просмотров под самым популярным видео на YouTube (шутка). В 2017 году для получения истинно случайных чисел в МГУ специально собрали квантовый генератор.

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

Что читать/смотреть: «Подробно о генераторах случайных и псевдослучайных чисел» (с примерами уязвимостей ГПСЧ в Java и PHP), «Как написать генератор случайных чисел» (на JS), «Случайные числа и функция random» на Arduino.

Гонка за ресурсами

Если ваше приложение запускает несколько потоков, важно следить, чтобы они не конфликтовали. «Состояние гонки» — это когда потоки или процессы наперебой обращаются к общим ресурсам и нарушают целостность данных. Например, один поток увеличил значение переменной на 1, второй — обнулил, первый — сохранил. Вместо ожидаемой единицы на выходе получаем 0. Чтобы этого не происходило, нужно ставить блокировки (block), которые не позволят второму и последующим потокам работать с уже занятыми ресурсами. Либо использовать другие механизмы синхронизации: семафоры, события, критические секции. Если сказанное вам не совсем понятно, самое время разобраться в теме.

Вебинары GB: «Синтаксический сахар при работе с потоками на языке С#».

Что читать: о многопоточности, «Синхронизация процессов», «Процессы и потоки in-depth».

Публичность и «все общее»

Чтобы не было сквозняка, нужно закрывать двери, в которые никто не ходит. Так же и с объектами внутри программы: они должны быть открыты только тем участкам кода, которым действительно нужны. Это один из принципов инкапсуляции, и он важен для всех, кто применяет объектно-ориентированное программирование. «Сквозняк» в коде ведет к ошибкам во взаимодействии объектов и повышает риск перехвата данных извне. Для ограничения доступа во многих языках применяют модификаторы public, private и protected. Также избегайте использования глобальных переменных везде, где можно обойтись без них. Делиться чем-то — хорошо, но только не внутри программы, когда общий доступ к ресурсам становится источником проблем.

Что читать: «Все о модификаторах доступа» (с примерами на C#), «Модификаторы доступа и класса» в Java.

Особенности сериализации

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

Сериализацию часто используют в веб-приложениях и службах, но не только. Например, если вы разрабатываете игру на C# или Python, сериализация поможет реализовать систему сохранений.

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

Если по какой-то причине вы не хотите передавать объект в виде потока байт, есть удобные альтернативы: можно сохранить структуру объекта в формат XML или JSON.

Чтобы не писать все с нуля, можно использовать готовые классы-сериализаторы:

  • BinaryFormatter — обеспечивает бинарную (двоичную) сериализацию, т.е. преобразование в поток байт;
  • SoapFormatter — сериализует связанные объекты в SOAP-сообщение: особым образом структурированный XML-документ, который пересылают по протоколам HTTP, HTTPS, SMTP и другим;
  • Xstream — Java-класс для сериализации в XML и JSON-файлы. XML весит меньше SOAP и быстрее передается по сети, но не так гибок в описании типов данных. Данные формата JSON являются объектами JavaScript и удобны для использования в клиент-серверных приложениях.

Что читать: «Как не наступить на грабли, работая с сериализацией», «Сериализация (C#)», «Сериализация объектов PHP», «Обобщенное копирование связных графов объектов в C# и нюансы их сериализации».

С-библиотеки без присмотра и переполнение буфера

Есть мнение, что в языках более высокого, чем С, уровня (Java, Python, C#, Rust) проблема переполнения буфера решена. Например, Java по умолчанию контролирует размер буфера и границы массивов. Но для ускорения жадных к ресурсам участков кода многие программисты используют С-библиотеки. И вот здесь будьте осторожны. Языки С и С++ считаются сложными именно потому, что дают простор для ошибок, которые легко допустить, но трудно найти. В плане переполнения буфера код на C/С++ очень уязвим.

В чем суть проблемы? Если неправильно рассчитать или не проверить размер буфера, программа попытается записать данные за его пределами. Это не только ошибка, но и дыра в безопасности. Злоумышленники могут намеренно переполнить буфер, чтобы подменить хранимый в стеке адрес возврата, т.е. адрес функции, которую надо вызвать по завершении текущей. Подставная функция передаст дальнейшее управление мошенническому коду. Такой подход используется при создании вирусов с 1988 года. Чтобы не наступить на ржавые 30-летние грабли, узнайте больше о том, как бороться с переполнением буфера.

Что читать: «Как устроены дыры в безопасности: переполнение буфера» (или оригинал на английском).

Переменные впрок

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

Вносите в алгоритм только то, что готовы инициализировать!

Хотите на таком-то этапе присвоить вот этой переменной вот это значение? Так и пишите. Что-то переиграли? Не забудьте почистить код.

Если не инициализировали переменную или функцию своевременно, вас может опередить взломщик. Будьте особенно внимательны с переменными, которые участвуют в аутентификации и других операциях, связанных с конфиденциальностью.

Что читать: «Переменные» (и где их объявляют — на примере С++), «Области видимости и замыкания в JavaScript».

Как найти остальные ошибки?

Читайте вторую часть статьи — она познакомит вас с инструментами и методами поиска логических ошибок в алгоритме и коде программы.

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

Пройти обучение

11 июл 18, 14:32
0 0
Статистика 1
Показы: 1 Охват: 0 Прочтений: 0

6 способов развить логическое мышление, если ты «гуманитарий

«Перепрошить» можно любой мозг. Всё благодаря нейропластичности — свойству меняться под воздействием опыта. И да, заголовок — кликбейт. Нет прирожденных «гуманитариев» и «технарей», есть только лень. Мы уже рассказывали, как развивать логическое мышление. В этой статье  — ещё несколько советов.

Спорьте!

Всегда ли вы можете доказать мнение, подобрав нужные аргументы? Бывало ли, что не получалось объяснить — чем фильм, который вы предлагаете посмотреть, лучше варианта друзей? Умение доказывать свою позицию не только полезно и выгодно (если речь о том, чтобы торговаться или аргументировать повышение зарпалты), но и развивает логику. Подбирая аргументы, анализируешь причины и следствия.

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

Спрашивайте себя «что?», «как?» и «почему?»

Упражнение, которое можно делать где и когда угодно:

  1. Выбираете случайный объект и задаете эти три вопроса относительно него;
  2. Четко и логично отвечаете на них. Если не получается — гуглите. Эта привычка — отдельный бонус, который увеличивает шансы стать крутым айтишником;
  3. Переходите к следующему объекту.

Слишком просто? Попробуйте! Удивитесь, сколько интересного не замечали.

Практикуйте SWOT-анализ

SWOT — это оценка сильных (Strengths) и слабых (Weaknesses) сторон, а также возможностей (Opportunities) и угроз (Threats) чего бы то ни было: идеи, предложения, объекта.

Как это делается, проще объяснить на примере. Допустим, вы подумываете поужинать в фаст-фуде. Сильные стороны: быстро, дешево, вкусно. Слабые: вредно, калорийно. Возможности: не надо готовить, освободится время на изучение программирования. Угрозы: можно отравиться или просто объесться, и ничего делать уже не захочется. Можно так же проанализировать вариант «писать код сейчас» или «взять ещё один фриланс». И сделать рациональный выбор.

Играйте в видеоигры

Чтобы развивать логику, не обязательно упражняться в скучных специализированных приложениях — решать головоломки и складывать пазлы. Прокачивать мозг можно, играя практически в любую стратегию, — например, «Starcraft» или «Civilization». Даже «Call of Duty» развивает способность к решению задач. Главное, чтобы игры не были единственным занятием — для тренировки мозга достаточно играть 3 раза в неделю по 20 минут.

Изучайте логику

Не спешите звать Капитана Очевидность. Многие забывают, что логика — наука, и её можно изучать. Олег Иванов, психолог, руководитель Центра урегулирования социальных конфликтов, советует начать с трудов Рене Декарта: «Изучение научных трудов мыслителей и философов способствует развитию логического мышления. Полезно и самому писать научные статьи. Использование индукции и дедукции в такой работе позволяет выработать алгоритм. Обобщение, анализ, вывод — всё это способствует развитию логического мышления».

От себя (ура, диплом философа пригодился!) рекомендую почитать Аристотеля. А тем, кто хочет поставить сложность на максимум, советую «Науку логики» Гегеля. Ещё можно разобраться, что такое круги Эйлера:

Шутка, основанная на кругах Эйлера.

Измеряйте неизмеримое — решайте задачи Ферми

Великий математик Энрико Ферми (тот самый, что знаменит парадоксом) считал, что за 60 секунд можно оценить абсолютно всё. Нужно знать лишь несколько фактов, чтобы примерно посчитать, сколько селфи делается в день на Земле, сколько раз в среднем люди нецензурно выражаются или ответить на другой подобный вопрос.

Объясню на примере. Допустим, вас спросили: «Сколько женщин подрабатывает, делая маникюр в Питере?». Чтобы дать приблизительный ответ, можно использовать такую логику:

  • Помню, что в Москве население около 12 миллионов. Питер где-то в 2 раза меньше — пусть будет 6 миллионов;
  • Половина знакомых девушек делает маникюр у специалиста, около 50% населения — женщины. Значит, в Питере потенциально 1,5 миллиона женщин «делают ноготочки» в салоне;
  • На маникюр ходят примерно раз в 2 недели — около 26 раз в год. Значит, за год в Питере это будет 1,5 млн * 26 раз = 39 миллионов раз;
  • На маникюр уходит примерно час — значит, один мастер может за день обслужить 8 клиенток;
  • Теперь нужно узнать, сколько требуется мастеров, чтобы успеть сделать маникюр 39 миллионов раз за год, при «производительности» 8 клиентов в день на одного специалиста. 39 млн / 365 дней / 8 раз в день = 13 356 мастеров маникюра в Питере.

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

Поэтому предлагаю начать тренироваться. Подумаем логически, сколько людей отказалось от своей мечты в пользу лени?


6 июн 18, 15:26
0 0
Статистика 1
Показы: 1 Охват: 0 Прочтений: 0
Показаны все темы: 3

Последние комментарии

нет комментариев
Читать

Поиск по блогу

Люди

7 пользователям нравится сайт lena2018.mirtesen.ru