Архив 10 июля 2019

Еще немного про Java, обучение программированию и все такое

Продолжу недавнюю запись про определение палиндромов на Java. Для начала, поступило довольно обоснованное замечание, что “в старой Java” класс String был устроен так, что методы типа trim и substring не копировали буфер строки, а работали с тем же (благо в Java строки “иммутабельны”) – поэтому квадратичной сложности тут не будет и в целом есть даже шансы, что все это дооптимизируется до почти правильного решения.

Что могу возразить? Во-первых, курс называется не “недокументированные возможности старой джавы”, а “общая информатика”, и читается отнюдь не в ПТУ для быдлокодеров. Я не берусь сказать, как именно будет реализован строковый тип в том языке программирования, который станет основным для выпускников через пять лет – да и вообще, будет ли это Java или что-то еще. Во-вторых – во вводном курсе программирования более ценно не умение обращаться с готовыми методами из класса String, а умение работать с массивами, например. Задачки наподобие “определите, является ли строка палиндромом (без учета пробелов)” – это абсолютная классика задач на работу с одномерным массивом.

И вот тут мы переходим к следующей части – а в чем так сказать, педагогическая ценность этого задания? Выполнить несколько почти тривиальных шагов, засунув их внутрь цикла? Неплохо для обучения Java junior по современным понятиям, но… Более традиционные (и результативные!) подходы предполагают обучение программированию “сержантским методом” (в терминологии [info]ailev) – много-много мелких, но содержательных задач, часть из которых объясняется преподавателем, а часть – остается для самостоятельного решения. Палиндромы, strrev, все что угодно – самостоятельной ценности у этих задач может в общем случае и не быть (хотя надо быть всегда готовым написать собственный left-pad), но их должно быть много, и они должны иметь разную сложность.

В том курсе, о котором я пишу, “сержантским методом” и не пахнет – задач предлагается смехотворно мало, а часть из них – в духе немецкого программистского образования – больше похожи на тесты для умственно отсталых, вот в духе “есть листочек, на листочке нарисованы паровоз, пароход, печка и самолет, что лишнее?” От того, что задача сформулирована в виде “изобразите UML-диаграмму для классов SteamLocomotive, SteamBoat, Furnace, LocalhostFurnace, Plane, BF109, BF109G”, она не становится более интеллектуальной, а от написания 10-20 строчек кода в неделю Fundamental programming skills, обещанные в описании курса, не появляются.

Несколько лет назад ни один околопрограммистский срач не обходился без ссылки на Джоэля Спольски – не буду изменять хорошей традиции и даже приведу целый абзац из Perils of Java Schools:

OOP in school consists mostly of memorizing a bunch of vocabulary terms like “encapsulation” and “inheritance” and taking multiple-choice quizzicles on the difference between polymorphism and overloading. Not much harder than memorizing famous dates and names in a history class, OOP poses inadequate mental challenges to scare away first-year students. When you struggle with an OOP problem, your program still works, it’s just sort of hard to maintain. Allegedly. But when you struggle with pointers, your program produces the line Segmentation Fault and you have no idea what’s going on, until you stop and take a deep breath and really try to force your mind to work at two different levels of abstraction simultaneously.

Уж очень мне нравится тут фразочка про inadequate mental challenges – и хочу обратить внимание, что какое-то обучение возможно только тогда, когда решаемые задачи достаточно сложны (а в некоторых вариациях “сержантского метода” – когда в каждом задании есть настолько сложные задачи, что никто не может их решить). Здесь же в своем “упрощенчестве” докатились до предела – задачи настолько тупы, что их решение просто не дает никаких полезных знаний.

PS Между прочим, некоторое количество вполне себе содержательных задач по программированию вместе с азами ООП содержатся в простом задании – “напишите свою реализацию класса java.lang.String”, или некоторого его подмножества. Для развлечения – можно предложить написать две реализации – одну в стиле “старой Java”, без копирования строкового буфера в методах типа substring, а вторую – с копированием, и сравнить производительность.