Dívidas são algo cotidiano. Empresas e pessoas contraem dívidas para operar seus negócios ou realizar sonhos e, com código é a mesma coisa. Vez ou outra admitimos algo fora dos padrões de qualidade para corrigir algum problema ou ter alguma vantagem sobre a concorrência.
O grande problema é quando essa nossa dívida sai do controle gerando prejuízos não só para a empresa mas, nós desenvolvedores. A falta de controle dessa dívida tem como maior efeito software rígido, difícil de entender e mudar, tornando-o mais suscetível a bugs. Para pagar o que devemos, temos que reverter esse quadro aumentando a facilidade em entender o código. Como fazer isso? Usando bons nomes!
2. Eu sou…
• Desenvolvedor;
• PHP, Ruby, Python e JavaScript. Um pouco de Java e C# (Xamarin);
• Trabalho na BriteCore;
• Trabalho remotamente desde 2016;
• @nelson_senna
6. Classificação de débito técnico
Imprudente Prudente
Deliberado
Negligente
“Não temos tempo!” “Precisamos entregar,
depois vemos isso.”
“O que é OOD? OOP? Design?”
“Agora sabemos que
devíamos ter feito isso.”
8. –Martin Fowler
“Como uma dívida
financeira, o débito
técnico incorre em
pagamentos de juros,
que vêm na forma de
esforço extra que
temos que fazer no
futuro...”
12. “Um Big Ball of Mud
tem estrutura
desordenada,
espalhada, desleixada,
remendada e, código
espaguete”
–Brian Foote
13. Big Ball of Mud
• Os nomes de variáveis e funções pouco informativos ou enganosos
• Uso extensivo de variáveis globais
• Métodos com longas listas de parâmetros mal definidos
• Métodos longos e complicados e, executam várias tarefas não relacionadas
• Código duplicado
• O fluxo de controle é difícil de entender e difícil de seguir
• A intenção do programador é quase impossível de discernir
• O código é simplesmente ilegível e espalhado sem modularização clara
• O código exibe os sinais inconfundíveis de patch após patch nas mãos de vários
mantenedores, cada um dos quais mal entendeu as conseqüências do que estava
fazendo
• Sem documentação
14. Big Ball of Mud
A estrutura rígida que faz com que o
código fique difícil de entender, caro e
suscetível a erros.
15. “Da sinonímia dos termos organização e
conhecimento, retira-se que a síntese mais
produtiva, ou mais instigadora, para a
construção de uma idéia acerca da
organização do conhecimento na sociedade é
aquela que abstrai de organização, pelo verbo
organizar, os sentidos de ORGANIZAR, que
são os seguintes: estabelecer as bases de;
arrumar de determinado modo; colocar em
certa ordem.”
–Renato Rocha Souza
23. Nomes são descobertos!
Sem nome
Sem
sentido
Honesto
Honesto e
Completo
Faz o que o
nome diz
Intenção
↪ ↪ ↪ ↪ ↪ ↪
Abstração de
domínio
24. Nomes são descobertos!
Sem nome
Sem
sentido
Honesto
Honesto e
Completo
Faz o que o
nome diz
Intenção
↪ ↪ ↪ ↪ ↪ ↪
Abstração de
domínio
@arlobelshee
25. var rootJquery,
// A simple way to check for HTML strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
// Shortcut simple #id case for speed
rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]+))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if (!selector) {
return this;
}
// Method init() accepts an alternate rootjQuery
// so migrate can support jQuery.sub (gh-2101)
root = root || rootjQuery;
// Handle HTML strings
if (typeof selector === "string") {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
…
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
return (context || root).find(selector);
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor(context).find(selector);
}
// HANDLE: $(DOMElement)
} else if (selector.nodeType) {
this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if (isFunction(selector)) {
return root.ready !== undefined ? root.ready(selector) :
// Execute immediately if ready is not present
selector(jQuery);
}
return jQuery.makeArray(selector, this);
};
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
// HANDLE: $(#id)
} else {
elem = document.getElementById(match[2]);
if (elem) {
// Inject the element directly into the jQuery object
this[0] = elem;
this.length = 1;
}
return this;
}
26. var rootJquery,
// A simple way to check for HTML strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
// Shortcut simple #id case for speed
rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]+))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if (!selector) {
return this;
}
// Method init() accepts an alternate rootjQuery
// so migrate can support jQuery.sub (gh-2101)
root = root || rootjQuery;
// Handle HTML strings
if (typeof selector === "string") {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
…
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
return (context || root).find(selector);
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor(context).find(selector);
}
// HANDLE: $(DOMElement)
} else if (selector.nodeType) {
this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if (isFunction(selector)) {
return root.ready !== undefined ? root.ready(selector) :
// Execute immediately if ready is not present
selector(jQuery);
}
return jQuery.makeArray(selector, this);
};
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
// HANDLE: $(#id)
} else {
elem = document.getElementById(match[2]);
if (elem) {
// Inject the element directly into the jQuery object
this[0] = elem;
this.length = 1;
}
return this;
}
28. var rootJquery,
// A simple way to check for HTML strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
// Shortcut simple #id case for speed
rquickExpr = /^(?:s*(<[wW]+>)[^>]*|#([w-]+))$/,
init = jQuery.fn.init = function( selector, context, root ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if (!selector) {
return this;
}
// Method init() accepts an alternate rootjQuery
// so migrate can support jQuery.sub (gh-2101)
root = root || rootjQuery;
// Handle HTML strings
if (typeof selector === "string") {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
…
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
return (context || root).find(selector);
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor(context).find(selector);
}
// HANDLE: $(DOMElement)
} else if (selector.nodeType) {
this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if (isFunction(selector)) {
return root.ready !== undefined ? root.ready(selector) :
// Execute immediately if ready is not present
selector(jQuery);
}
return jQuery.makeArray(selector, this);
};
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
// HANDLE: $(#id)
} else {
elem = document.getElementById(match[2]);
if (elem) {
// Inject the element directly into the jQuery object
this[0] = elem;
this.length = 1;
}
return this;
}
Sem nome!
29. var handleHTMLStrings = function (selector, context, root) {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
// ...
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
// ...
}
return this;
// HANDLE: $(#id)
} else {
// ...
}
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
// ...
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
// ...
}
}
Sem sentido!
30. var handleHTMLStrings = function (selector, context, root) {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
// ...
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
// ...
}
return this;
// HANDLE: $(#id)
} else {
// ...
}
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
// ...
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
// ...
}
}
31. var handleHTMLStringsAndElementIds = function (selector, context, root) {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
// ...
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
// ...
}
return this;
// HANDLE: $(#id)
} else {
// ...
}
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
// ...
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
// ...
}
}
Honesto!
32. var handleHTMLStringsAndElementIds = function (selector, context, root) {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
// ...
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
// ...
}
return this;
// HANDLE: $(#id)
} else {
// ...
}
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
// ...
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
// ...
}
}
33. var handleHTMLStringsAndElementIdsAndCSSSelectors = function (selector, context, root) {
if (selector[0] === "<" && selector[selector.length - 1] === ">" && selector.length >= 3) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [null, selector, null];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
// ...
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
// ...
}
return this;
// HANDLE: $(#id)
} else {
// ...
}
// HANDLE: $(expr, $(...))
} else if (!context || context.jquery) {
// ...
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
// ...
}
}
Honesto e
completo!
37. handleHTMLStringsAndElementIdsAndCSSSelectors = function (selector, context, root)
handleHTMLStrings = function (selector, context, root)
handleElementIds = function (selector, context, root)
handleCSSSelectors = function (selector, context, root)
38. handleHTMLStringsAndElementIdsAndCSSSelectors = function (selector, context, root)
handleHTMLStrings = function (selector, context, root)
handleElementIds = function (selector, context, root)
handleCSSSelectors = function (selector, context, root)
39. var handleHTMLStrings = function (match, context, root) {
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
}
}
}
Sem sentido?
40. Características de bons nomes de métodos
• Use verbos*;
• Sem abreviações;
• Conciso (mas, não muito!);
• Use perguntas quando o método retornar um boolean (isEmpty(), canDo());
• Não use “And” e “Or” nos seus nomes;
• Não deixe o nome redundante com o argumento
($list->addItem($item));
• Não deixe o nome redundante com quem chama
($list->addToList($item));
41. var buildFromHTMLString = function (match, context, root) {
// Match html or make sure no context is specified for #id
if (match && (match[1] || !context)) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
}
}
}
42. Temos um bom nome?
• Use verbos* √
• Sem abreviações √
• Conciso (mas, não muito!) √
• Use perguntas quando o método retornar um boolean (isEmpty(), canDo()) √
• Não use “And” e “Or” nos seus nomes √
• Não deixe o nome redundante com o argumento √
• Não deixe o nome redundante com quem chama √
43. var fromHTMLString = function (match, context) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
}
}
44. var fromHTMLString = function (match, context) {
// HANDLE: $(html) -> $(array)
if (match[1]) {
context = context instanceof jQuery ? context[0] : context;
// Option to run scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge(this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
));
// HANDLE: $(html, props)
if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {
for (match in context) {
// Properties of context are called as methods if possible
if (isFunction(this[match])) {
this[match](context[match]);
// ...and otherwise set as attributes
} else {
this.attr(match, context[match]);
}
}
}
return this;
}
}
45. Características de bons nomes de variáveis
• Sem abreviações;
• Conciso (mas, não muito!);
• Use perguntas quando o método retornar um boolean (isEmpty, canDo);
• Não use “And” e “Or” nos seus nomes;
46. var fromHTMLString = function (source, context) {
if (source) {
context = context instanceof jQuery ? context[0] : context;
var runScriptsForBackCompat = true
var isNode = context && context.nodeType;
var contextDocument = isNode ? context.ownerDocument || context : document;
jQuery.merge(this, jQuery.parseHTML(source, contextDocument, runScriptsForBackCompat));
var isSingleTagWithoutAttributes = rsingleTag.test(source)
var isAttributesSuperSet = jQuery.isPlainObject(context);
if (isSingleTagWithoutAttributes && isAttributesSuperSet) {
for (attributeName in context) {
// Properties of context are called as methods if possible
if (isFunction(this[attributeName])) {
this[attributeName](context[attributeName]);
// ...and otherwise set as attributes
} else {
this.attr(attributeName, context[attributeName]);
}
}
}
return this;
}
}
47. var fromHTMLString = function (source, context) {
if (source) {
context = context instanceof jQuery ? context[0] : context;
var runScriptsForBackCompat = true
var isNode = context && context.nodeType;
var contextDocument = isNode ? context.ownerDocument || context : document;
jQuery.merge(this, jQuery.parseHTML(source, contextDocument, runScriptsForBackCompat));
var isSingleTagWithoutAttributes = rsingleTag.test(source)
var isAttributesSuperSet = jQuery.isPlainObject(context);
if (isSingleTagWithoutAttributes && isAttributesSuperSet) {
for (attributeName in context) {
// Properties of context are called as methods if possible
if (isFunction(this[attributeName])) {
this[attributeName](context[attributeName]);
// ...and otherwise set as attributes
} else {
this.attr(attributeName, context[attributeName]);
}
}
}
return this;
}
}
48. Características de bons nomes de classes
• Use substantivos e adjetivos*;
• Não use muitos adjetivos (AbstractFactoryPatternBase);
• Não use “Manager”, “Helper” e “Data” nos seus nomes;
• Não use Sufixos do seu namespace (Service, Factory, Iterator)
• Evite usar “er”, “or”, “tion”, “sion” (ObjectFinder, DataProcessor, Conversion,
DataManipulation)
51. var HTMLCreationContext = function (elementOrAttributes) {
this.elementOrAttributes = elementOrAttributes;
}
HTMLCreationContext.prototype = {
isNode: function () {
return (this.elementOrAttributes && this.elementOrAttributes.nodeType);
},
getOwnerDocument: function () {
if (this.isNode) {
return this.elementOrAttributes.ownerDocument || this.elementOrAttributes;
}
return document;
},
getAttributesSuperSet: function () {
if (jQuery.isPlainObject(this.elementOrAttributes)) {
return this.elementOrAttributes;
}
return {};
},
getMergeContext: function () {
if (this.elementOrAttributes instanceof jQuery) {
return this.elementOrAttributes[0];
}
return this.elementOrAttributes;
}
}
52. var HTMLCreationContext = function (elementOrAttributes) {
this.elementOrAttributes = elementOrAttributes;
}
HTMLCreationContext.prototype = {
isNode: function () {
return (this.elementOrAttributes && this.elementOrAttributes.nodeType);
},
getOwnerDocument: function () {
if (this.isNode) {
return this.elementOrAttributes.ownerDocument || this.elementOrAttributes;
}
return document;
},
getAttributesSuperSet: function () {
if (jQuery.isPlainObject(this.elementOrAttributes)) {
return this.elementOrAttributes;
}
return {};
},
getMergeContext: function () {
if (this.elementOrAttributes instanceof jQuery) {
return this.elementOrAttributes[0];
}
return this.elementOrAttributes;
}
}
53. var fromHTMLString = function (source, context) {
if (source) {
var creationContext = new HTMLCreationContext(context);
var runScriptsForBackCompat = true
jQuery.merge(this, jQuery.parseHTML(
source,
creationContext.getOwnerDocument(),
runScriptsForBackCompat
));
var isSingleTagWithoutAttributes = rsingleTag.test(source)
if (isSingleTagWithoutAttributes) {
var attributes = creationContext.getAttributesSuperSet();
for (attributeName in attributes) {
// Properties of context are called as methods if possible
if (isFunction(this[attributeName])) {
this[attributeName](context[attributeName]);
// ...and otherwise set as attributes
} else {
this.attr(attributeName, context[attributeName]);
}
}
}
return this;
}
}
54.
55. Concluindo…
1. Procure por lugares com mais de um nível de identação
2. Extraia e dê nome para o que fizer sentido
3. Descreva o que o método realmente faz, sem vergonha, seja honesto!
4. Vá diminuindo o nome do método extraindo suas responsabilidades
5. Excesso de métodos privados para um dado contexto: Uma nova classe!
6. Procure na documentação ou fale com um amiguinho para descrever
o problema de domínio resolvido
7. Seja feliz!