(function (win) {
win.CharCounter = _ = {
Init: function (globalEventHandlerRegistrationFunction, eventCallbacks) {
if (globalEventHandlerRegistrationFunction) {
var cls = "counter";
globalEventHandlerRegistrationFunction("keyup", onKeyUp, cls);
globalEventHandlerRegistrationFunction("focusin", onFocusIn, cls);
globalEventHandlerRegistrationFunction("focusout", onFocusOut, cls);
} else {
$("input.counter")
.not(".counter-init")
.addClass("counter-init")
.keyup(ShowCounter)
.focus(ReShowCounter)
.blur(HideCounter);
$("textarea.counter")
.not(".counter-init")
.addClass("counter-init")
.keyup(ShowCounter)
.focus(ReShowCounter)
.blur(HideCounter);
}
AssignCallbacks(eventCallbacks);
},
StateEnum: {
notInitialized: 0,
notShown: 1,
basic: 2,
highlighted: 3,
warn: 4,
max: 5,
exceeded: 6
},
AssignEventCallback: function (eventName, callbackFunction) {
if (eventName) {
var obj = {};
obj[eventName] = callbackFunction;
AssignEventCallbacks(obj);
}
},
Refresh: function (element) {
if (typeof element == "string" && element.length && element.substr(0, 1) != "#")
element = "#" + element;
element = $(element || $("body"));
var tag = element[0].tagName.toUpperCase(),
list;
if (((tag == "INPUT" && element.attr("type") == "text") || tag == "TEXTAREA") && element.hasClass("counter"))
list = [element];
else
list = element.find("input[type=text].counter,textarea.counter");
if (list.length) {
$.each(list, function () {
var el = $(this);
ShowCounter(el);
if (!el.is(":focus"))
HideCounter(el, true);
});
}
}
};
$.fn.extend({
hasClasses: function (selectors) {
var self = this;
for (var i in selectors) {
if ($(self).hasClass(selectors[i]))
return true;
}
return false;
}
});
function AssignCallbacks(defObj) {
if (defObj) {
var obj = m_oCallbacks,
p;
for (p in defObj) {
if (typeof obj[p] == "function")
obj[p] = defObj[p] || Dummy;
}
}
}
function HideCounter(el, skipEvent) {
var data = el.data("counter-data"),
cbk;
if (data && data.isShown && (!data.isTextArea || el.hasClass("counter-hide-on-blur"))) {
el.css("padding-right", data.origPadding);
el.next().hide();
data.hidden = true;
cbk = m_oCallbacks;
if(cbk.shown && !skipEvent)
cbk.shown(el, data);
}
}
function ReShowCounter(el) {
var data = el.data("counter-data"),
counter = el.next(),
cbk;
if (data) {
ShowCounter(el);
el.css("padding-right", data.padding);
if (counter.length)
counter[0].style.removeProperty("display");
data.hidden = false;
if (data.isShown && (!data.isTextArea || el.hasClass("counter-hide-on-blur"))) {
cbk = m_oCallbacks;
if (cbk.shown)
cbk.shown(el, data);
}
}
}
function ShowCounter(el) {
var counter = el.next(),
origData = el.data("counter-data"),
prev = PrevData(origData),
data = CharThresholds(el, origData),
count,
divs,
parent;
if (!counter.length) {
parent = el.parent();
if (parent.css("position") == "static")
parent.css("position", "relative");
counter = $("
" + data.maxAt + "
");
el.after(counter);
count = counter.find(">div");
divs = count.find("div");
if (!data.isTextArea && el.hasClass("counter-vertical")) {
counter.addClass("counter-vertical");
divs.css("display", "block");
} else {
divs.css("display", "inline-block");
}
} else {
count = counter.find(">div");
divs = count.find("div");
}
var v = el.val(),
len = v.length,
hasMax = data.maxAt != 0,
hasWarn = data.warnAt > -1,
hasHl = data.highlightAt > -1,
hasBasic = data.basicAt > -1,
add = data.stopAtMax ? 0 : 1,
showMax = hasMax && len >= (data.maxAt + add),
showWarn = !showMax && hasWarn && len < (data.maxAt + add) && len >= data.warnAt,
showHl = !showMax && !showWarn && hasHl && len < data.warnAt && len >= data.highlightAt,
showBasic = !showMax && !showWarn && !showHl && hasBasic && (!hasHl || len < data.highlightAt) && len >= data.basicAt,
classes = [data.basicClass, data.hlClass, data.warnClass, data.maxClass],
wasShown = counter.hasClasses(classes),
showAny = showBasic || showHl || showWarn || showMax,
visibilityChange = showAny != wasShown;
if (!data.maxAt || data.maxAt < 0)
return; // Do not show the counter as the required max length has net been defined.
data.isShown = showAny;
data.isBasic = showBasic;
data.isHighlighted = showHl;
data.isWarn = showWarn;
data.isMax = showMax;
data.prevCount = prev.count;
data.prevRemainingCount = prev.remaining;
data.prevState = prev.state;
ExecuteCallbacks(el, prev, data, showAny, showBasic, showHl, showWarn, showMax);
divs.eq(0).text(data.curCount);
if (!data.isTextArea || !visibilityChange || showAny) {
if (data.isTextArea && showAny)
counter.slideDown();
counter
.toggleClass(data.maxClass, showMax)
.toggleClass(data.warnClass, showWarn)
.toggleClass(data.hlClass, showHl)
.toggleClass(data.basicClass, showBasic);
}
if (data.isTextArea) {
if (wasShown != showAny) {
if (showAny)
counter.hide().slideDown();
else
counter.slideUp(function () {
counter.removeClass(classes);
});
}
}
if (!data.isTextArea) {
data.padding = counter.width() + 20;
el.css("padding-right", data.padding);
}
}
function CharThresholds(el, data) {
var len = el.val().length;
if (!data) {
var def = el.data("counter-def"),
vals,
max = el.attr("maxlength"),
isTextArea = el[0].nodeName.toUpperCase() == "TEXTAREA";
if (max)
max *= 1;
if (def)
vals = def.split(",");
else
vals = [];
data = {
id: el.attr("id"),
basicAt: CalcThreshold(max, vals, 0, isTextArea ? "50%" : "x"),
highlightAt: CalcThreshold(max, vals, 1, "80%"),
warnAt: CalcThreshold(max, vals, 2, isTextArea ? "90%" : -3),
maxAt: CalcThreshold(max, vals, 3, max),
stopAtMax: max || (vals && vals.length && vals.length > 3 && vals[3] && vals[3] > 0),
basicClass: CounterClass(vals, 4, "counter-basic"),
hlClass: CounterClass(vals, 5, "counter-hl"),
warnClass: CounterClass(vals, 6, "counter-warn"),
maxClass: CounterClass(vals, 7, "counter-max"),
isTextArea: isTextArea,
origPadding: el.css("padding-right"),
prevCount: -1,
prevRemainingCount: 0,
remainingCount: 0,
prevState: _.StateEnum.notInitialized,
curState: _.StateEnum.notInitialized,
isShown: false,
isBasic: false,
isHighlighted: false,
isWarn: false,
isMax: false
};
el.data("counter-data", data);
if (data.stopAtMax)
el.attr("maxlength", data.maxAt);
}
data.curCount = len;
data.remainingCount = data.maxAt - len;
data.isExceeded = data.remainingCount >= 0;
return data;
}
function CounterClass(vals, idx, defaultVal) {
return vals && vals.length > idx && vals[idx].trim().length ? vals[idx].trim() : defaultVal;
}
function CalcThreshold(max, vals, idx, defaultVal) {
if (!vals)
return (defaultVal * 1) || -1;
else {
if (vals.length < (idx + 1) || !vals[idx].length)
vals[idx] = defaultVal + "";
var val = vals[idx].trim(),
neg = false,
m = max || Math.abs(!isNaN(vals[3]) ? vals[3] * 1 : 0);
if (isNaN(val))
return -1;
if (val.substr(0, 1) == "-") {
neg = true;
val = val.substr(1);
}
if (val.substr(val.length - 1) == "%") {
if (m > 0) {
val = Math.round(m * (val.substr(0, val.length - 1) / 100));
} else {
return -1;
}
}
if (neg && idx < 3) { // idx == 0 means that val is the max value which, if negative, just means that it is a hard stop and we still return the absolute value of
if (m > 0)
return m - val;
else
return -1;
} else
return val * 1;
}
}
function PrevData(data) {
var prev = m_oPrevData;
if (data) {
prev.count = data.curCount;
prev.remaining = data.remainingCount;
prev.state = data.curState;
prev.shown = data.isShown;
prev.basic = data.isBasic;
prev.hl = data.isHighlighted;
prev.warn = data.isWarn;
prev.max = data.isMax;
prev.exceeded = data.isExceeded;
} else {
prev.count = -1;
prev.remaining = -1;
prev.state = _.StateEnum.notInitialized;
prev.shown = false;
prev.basic = false;
prev.hl = false;
prev.warn = false;
prev.max = false;
prev.exceeded = false;
}
return prev;
}
function ExecuteCallbacks(el, prev, cur, showAny, showBasic, showHl, showWarn, showMax) {
var cbk = m_oCallbacks,
count = prev.count != cur.curCount,
state = prev.state != cur.curState,
shown = showAny != (prev.basic || prev.hl || prev.warn || prev.max),
basic = prev.basic != showBasic,
hl = prev.hl != showHl,
warn = prev.warn != showWarn,
max = prev.max != showMax,
exceeded = prev.exceeded != cur.isExceeded;
if (count)
cbk.countChanged(el, cur);
if (state)
cbk.stateChanged(el, cur);
if (shown)
cbk.shown(el, cur);
if (basic)
cbk.basic(el, cur);
if (hl)
cbk.highlighted(el, cur);
if (warn)
cbk.warn(el, cur);
if (max)
cbk.warn(el, cur);
if (exceeded)
cbk.exceeded(el, cur);
}
})(window);
Obsah

Explaining to a layman how long (good) programming really takes

Jak vysvětlit laikovi jak dlouho programování (kvalitního) softwaru opravdu trvá

Aneb "Ne mami, opravdu tohle nemůžu vytvořit za den."

Napsal Marek Všechovský, Listopad 2020

Během mé dlouhé profesionální kariéry softwarového vývojáře jsem se setkal s mnoha lidmi, kterým jsem se v určitou chvíli snažil vysvětlit, jak moc časově náročné programování ve skutečnosti je a proč. Lidé nemají NEJMENŠÍ POTUCHU jaká je realita v tomto odvětví.

Pokud jsi nad tím někdy přemýšlel(a), tento článek je pro tebe.

A pokud jsi sám programátorem, možná ti tento článek pomůže tím, že budeš mít k dispozici odkaz, který budeš moci lidem nabídnout, pokud se nalezneš ve stejné situaci. Lidé obecně opravdu nemají ohledně tohoto nejmenší tušení, které je jakkoliv blízko k realitě.

Vysvětlit toto je těžké. Dokonce i my programátoři, tedy lidé, co by měli mít tu nejlepší představu, podhodnocujeme naše časové odhady k implementaci nové funkce programu. A to děláme překvapivě velice často, aniž by jsme to dělali záměrně. Dokonce někdy i zapochybujeme o schopnostech jiného programátora když nám řekne:
"Tohle nebo tamto mi trvalo naprogramovat tolik a tolik hodin, dní, týdnů...".
"Proč tak dlouho? To bych zvládl napsat za polovičku - čtvrtinu - desetinu toho času."

Ale tenhle názor nám mnohdy vydrží jen do chvíle, dokud to nezkusíme naprogramovat sami, nebo se pokusíme existující kód vylepšit. Kód jde vylepšit vždy, ale často nám docvaknou časově náročné maličkosti a záludnosti, které nás rovnou nenapadly. A aby byl výsledek naší práce opravdu dobrý, je to hodně právě o těch "neviditelných" maličkostech.

No každopádně jedním z těch věčných pochybovačů se zdá být moje mamka. Můžu se to pokusit vysvětlit stokrát a dát nekonečně mnoho příkladů. Například ten, kde, jak jsem před lety četl, Microsoft má / měl v ten čas zhruba 100 programátorů pracujících jen na textovém editoru MS Word...

"Ten mami znáš ne?... Ten program co umožňuje lidem psát text na počítači už od roku 1983. A přesto, o více jak 40 let později, stále ještě do něj přidávají funkce. Funkce, které většina lidí nemá ani ponětí, že v tom programu existují. A STO lidí pracuje jen na tomhle ještě dnes. A ten počet lidí se postupně zvyšuje už od roku 1983!! Mami, představ si ten počet odpracovaných pracovních hodin jen na tomhle jednom jediném, na první pohled jednoduchém, programu!! Fakt, zkus si to opravdu představit!! Umíš si to představit? Oni za 3 a půl dne odpracují tolik hodin, kolik by zvládl osamocený člověk odpracovat za celý rok, i kdyby dělal všechny víkendy a neměl vůbec žádnou dovolenou! Já jsem takový osamocený člověk, který dělá na svých projektech zcela sám od nápadu přes design a implementaci až ke kontrole kvality. Takže mohu odpracovat jeden pracovní rok... no, asi tak zhruba za rok. (No, možná ve skutečnosti docela znatelně méně díky tomu, že tomu věnuji asi více než délku běžné pracovní doby a díky tomu, že nemusím chodit na schůze, koordinovat svou práci s ostatními atd., ale to už je trošku mimochodem. Stále nemohu udělat ani z velkého daleka tolik práce jako 100 lidí za stejný časový úsek. To by mělo být celkem jasné. A sice nemám schůze atp., zato se musím starat o firmu, starat se o provoz serverů, platit věci, dělat účetnictví, reportovat pravidelně státu, nakupovat sám, co potřebuji neb nemůžu jen říci nějakému firemnímu nákupnímu oddělení nebo manažerovi atd. atd.) A ty jsi překvapená že můj velký softwarový projekt který designuji a programuji z gruntu a sám ještě není za 2 měsíce hotový?" :-)

No, asi už teď nemusím opakovat, že toto vysvětlení se nezdá, že by nějak obzvláště pomáhalo a já slýchám stejné překvapení od mé matky v průběhu let čas od času znovu a znovu.

A to je přesně ten důvod, proč jsem se rozhodl napsat tento článek.

To a ten fakt, že jsem právě dokončil práci na nové komponentě, která řeší problém, který může být v podstatě vyřešen v pouhých pár řádcích kódu ve zhruba 10ti minutách a přesto mi její naprogramování zabralo celé 3 dny! Zde je odpověď na otázku "Jak to!?"...

Úvod

Problém, který tu řešíme

To nejjednodušší řešení

Kompletnější řešení

Tak a teď víte

A je toho víc