Upload
agile-spain
View
657
Download
1
Embed Size (px)
DESCRIPTION
Leo Antoli
Citation preview
Quiero hacer ágil, ¿ y ahora qué: Java, Ruby o Scala ?
Leo Antoli
@lantoli
Conferencia Agile-Spain 2011 - Castellón
Antes de empezar
• Hacer ágil o ser ágil, esa es la cuestión• ¿ Se puede Cobol ágil o Ruby sin tests y en cascada ?• ¿ Se puede ser ágil con Java ?• El lenguaje es muy importante... pero es sólo una
herramienta• ¿ Por qué Java, Scala y Ruby ?
• ¿ Cuáles son las buenas prácticas técnicas (aplicables a ágil y cascada ?
• ¿ Se pueden usar nuevos lenguajes en tus desarrollos actuales ?
Fuera de la sesión
• Lenguajes en cliente• Frameworks• Sólo se tratan algunos lenguajes
Un lenguaje para dominarlos a todos
Popularidad
1 - Java2 - C3 - C++4 - C#5 - PHP6 - Objective-C7 - Visual Basic8 - Python9 - Perl
10 - Javascript11 - Ruby25 - COBOL27 - Scheme (LISP)30 - Fortran35 - Haskell40 - Prolog50 - Scala>51 Groovy
PopularidadGoogle Code Jam 2011
Causa/efecto o correlación
Sopa de letras
Diseño simple, arquitectura
- Pasa todas las pruebas (hace lo que tiene que hacer, pero no más)- Minimiza las duplicaciones (si quiero cambiar el comportamiento lo hago en un solo sitio)- Maximiza la claridad (expresa bien las intenciones, es fácil de entender)- Es conciso (usa el menor número de clases y métodos, cumpliendo las otras reglas)
Estos mandamientos se resumen en dos:- Quitar duplicación de código - Mejorar los nombres mal puestos
Arquitectura: El conjunto de decisiones de diseño significativas (costosas de cambiar)
Entrega continua de valor: ¿ sólo con pruebas automáticas ?
¿ TDD/BDD obligatorio ? ...Anda antes de correr
Dinámico o Estático
- Estáticos tienen problemas, muchas redundancias y son menos legibles que los dinámicos
- La solución son los dinámicos o hay algo entre medias ?
Conciso o verboso...public class StringCalculator {
public int add(String inputStr) { String firstLine = getLineDelimiters(inputStr); String textWithNumbers = getStringWithoutDelimiterLine(inputStr); List<String> delimiters = getDelimiters(firstLine); List<Integer> numbers = getAllowedNumbers(getNumbers(textWithNumbers, delimiters)); checkNegativeNumbers(numbers); return getSum(numbers);}...
private static int getSum(List<Integer> numbers) { SUMA LOS ELEMENTOS DE UNA LISTA int sum = 0; for (int num : numbers) { sum += num; } return sum;}
private static void checkNegativeNumbers(List<Integer> numbers) throws IllegalArgumentException { List<Integer> negatives = new ArrayList<Integer>(); DECLARAR VARIABLE for (int num : numbers) { LISTA NUMEROS NEGATIVOS if (num < 0) { negatives.add(num); } } if (negatives.size() > 0) { throw new IllegalArgumentException("no se permiten negativos: " + negatives.toString()); }}...
121 líneas
Conciso o verbosoclass Integer def negative? self < 0 end def suitable_for_string_calculator? self <= 1000 end end
class Calculator def add(args) strnumbers, delimiter = extract_strnumbers_and_delimiter args numbers = get_number_list strnumbers, delimiter get_only_suitable_numbers numbers check_negatives_numbers numbers numbers.inject 0, :+ SUMA LOS ELEMENTOS DE UNA LISTA end
def get_number_list(numbers_str, delimiter) numbers_str.split(delimiter).collect { |num| num.to_i } end
def get_only_suitable_numbers(numbers) numbers.select! &:suitable_for_string_calculator? end
def check_negatives_numbers(numbers) negatives = numbers.select &:negative? LISTA NUMEROS NEGATIVOS raise "negatives not allowed (#{negatives.join(', ')})" if negatives.any? IF AL FINAL end
def extract_strnumbers_and_delimiter(args) delimiters = /[\n,]/ text = args.dup first_line = text.slice!(%r{^//(.+)\n}) delimiters = first_line.scan(%r{\[([^\]]+)\]}).flatten << delimiters if first_line return text, Regexp.union(delimiters) endend
43 líneas
Conciso o verboso...
@Testpublic void testAddEmpty() throws Exception { assertEquals("adding empty string", 0, calc.add(""));}
@Testpublic void testSingleElement() throws Exception { assertEquals("adding simple element", 1, calc.add("1")); assertEquals("adding more simple elements", 345, calc.add("345"));}
@Testpublic void testTwoElementsSum() throws Exception { assertEquals("adding two elements", 3, calc.add("1,2")); assertEquals("adding two more elements", 201, calc.add("123,78"));}
@Testpublic void testNewLineDelimitier() throws Exception { assertEquals("adding with different delimiter", 6, calc.add("1\n2,3"));}
PROBANDO EXCEPCIONES@Testpublic void testNegativesThrowsException() throws Exception { try { calc.add("6,-8,3,-52"); fail("testing negative numbers, shouldn't be here"); } catch (Exception e) { String msg = e.getMessage(); assertTrue("contains negative sentence", msg.contains("no se permiten negativos")); assertTrue("contains negative number", msg.contains("-8")); assertTrue("contains negative number", msg.contains("-52")); }}...
Conciso o verbosodescribe "String calculator" do before do @calculator = Calculator.new end
it "empty should be 0" do @calculator.add("").should == 0 end
PROBANDO VARIOS CASOS CON HASHTABLE { "" => 0, "1" => 1, "345" => 345, "1,1" => 2, "3,4" => 7, "1,1,1" => 3, "1,2,3" => 6, "5\n2\n3" => 10, "123,78" => 201}.each do | numbers, result | it "adding #{numbers} should be #{result}" do @calculator.add(numbers).should == result end end
it "delimiter , and \n should work" do @calculator.add("1\n2,3").should == 6 end
it "different delimiters specified in first line should work" do @calculator.add("//[%]\n2%6").should == 8 @calculator.add("//[;]\n1;2").should == 3 @calculator.add("//[+]\n8+12,43").should == 63 end
it "doesn't allow negative numbers" do PROBANDO EXCEPCIONES expect { @calculator.add("1\n-2\n-3\n4") }.to raise_error(Exception, "negatives not allowed (-2, -3)") end
it "big numbers should be ignored" do @calculator.add("2,1001").should == 2 @calculator.add("2,1000").should == 1002 end...
Conciso o verboso...ROMANS = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 }
class Fixnum def to_roman return nil unless self > 0 && self < 4000 remaining_number = self ROMANS.inject ("") do | roman_str, current_number | times,remaining_number = remaining_number.divmod current_number[1] roman_str + current_number[0].to_s * times end endend
TRANSFORMATIONS = { I: 1, II: 2, III: 3, IV: 4, V: 5, VI: 6, VII: 7, VIII: 8, IX: 9, X: 10, XI: 11, XII: 12, XIV: 14, XV: 15, XIX: 19, XXXIX: 39, XL: 40, XLI: 41, L: 50, LXXXIX: 89, XC: 90, XCIX: 99, C: 100, CCCXCIX: 399, CD: 400, D: 500, DCCCXCIX: 899, CM: 900, M: 1000, MMXI: 2011, MMMCMXCIX: 3999}
describe "From arabic to roman numerals. " do TRANSFORMATIONS.each do |roman, arabic| it("transforms #{arabic} to #{roman}") do arabic.to_roman.should == roman.to_s end end
[ -10, 0, 4000, 4100 ].each do | bad_arabic | it("#{bad_arabic} can not be transformed to roman numeral") do bad_arabic.to_roman.should == nil end endend
Llamar métodos por nombre / introspecciónclass Integer
def to_fizzbuzz1 return "FizzBuzz" if fizzbuzz? return "Fizz" if fizz? return "Buzz" if buzz? self end
FIZZBUZZ3 = { fizzbuzz?: "FizzBuzz", buzz?: "Buzz", fizz?: "Fizz"}
def to_fizzbuzz3 FIZZBUZZ3.each do |method , name| return name if send method end self end
def multiple_of? n self % n == 0 end
def fizz? multiple_of? 3 end ... def fizzbuzz? multiple_of? 15 end
Llamar métodos por nombre / introspeccióndescribe "FizzBuzz identity cases" do
it "1 should be 1" do 1.to_fizzbuzz.should == 1 end it "4 should be 4" do 4.to_fizzbuzz.should == 4 end
end...
describe "FizzBuzz FizzBuzz cases" do
it "15 should be Buzz" do 15.to_fizzbuzz.should == "FizzBuzz" end...end
describe "FizzBuzz testing all implementations" do [:to_fizzbuzz1, :to_fizzbuzz2, :to_fizzbuzz3].each do |impl| (1..100).each do |n| it "trying impl. #{impl} for number #{n}" do n.send(impl).should == n.to_fizzbuzz end end endend
Inferencia de tipos
Java
int x = 1 + 2 * 3;
String y = x.toString();
List<String> lista = new ArrayList<String>();Map<String, Integer> m = new HashMap<String, Integer>();
public int incrementar(int x) {return x + 1;}
Java 7:
Map<String, List<String>> myMap = new HashMap<>();
Scala
val x = 1 + 2 * 3
val y = x.toString() val lista : List[String] = new ArrayList[String]val lista = new ArrayList[String]val lista = List val lista = List("elm1", "elm2")
val m = new HashMap[String,Int]
def incrementar(x: Int) : Int = x + 1def incrementar(x: Int) = x + 1
Tipos covariantes y contracovariantes, not reified (type erasure), ...Java:
Vehiculo[] v = new Vehiculo[10];Coche[] c = new Coche[10];v = c; // CORRECTO
List<Vehiculo> vlist = new ArrayList<Vehiculo>();List<Coche> vcoche = new ArrayList<Coche>();vlist = vcoche; // ERROR - Type mismatch: cannot convert from List<Coche> to List<Vehiculo>
Scala:
class Stack[+A] { def push[B >: A](elem: B) ....
Abrir clases o conversiones implícitas (vistas)2 days ago 5 days from_now
class DateHelper(number: Int) { def days(when: String) : Date = { var date = Calendar.getInstance() when match { case "ago" => date.add(Calendar.DAY_OF_MONTH, -number) case "from_now" => date.add(Calendar.DAY_OF_MONTH, number) case _ => date } date.getTime() } }
implicit def convertInt2DateHelper(number: Int) = new DateHelper(number)
final class RichChar(c: Char) { def isDigit: Boolean = Character.isDigit(c) // isLetter, isWhitespace, etc. }
object RichCharTest { implicit def charWrapper(c: char) = new RichChar(c) def main(args: Array[String]) { println('0'.isDigit) }}
Duck typing, Interfaces o Structural typingclass Duck { def quack = println("Quaaaaaack !") def feathers = println("The duck has white and gray feathers.")
}
class Person { def quack = println("The person imitates a duck.") def feathers = println("The person takes a feather from the ground and shows it.") }
...
def inTheForest(duck: { def quack; def feathers }) = { duck.quack duck.feathers }
type ActAsADuck = { def quack def feathers}
Missing method, invokedynamic (java7), Dynamic traitclass Roman def romanToInt(str) # ... end def method_missing(methId) str = methId.id2name romanToInt(str) endend
r = Roman.newr.iv #=> 4r.xxiii #=> 23r.mm #=> 2000
miXml.persona.nombre , o miJson.persona.nombre ;-)
Rails lo usa muchísimo, por ejemplo find_by_columns
Módulos, mixins, traits, herencia múltiple, ...class miclase{ include Enumerable # tengo gratis inject, all?, any?, collect...
def each ....}
Comparable, con <=> tengo <, <=, ==, >, >=, between?
Puedo mezclar los módulos que quiera
JVM
GroovyScalaJRubyJythonClojure...
Quien lo iba a imaginar
Rendimiento¿ Compilado o interpretado ?¿ Estáticos o dinámicos ? ¿ Intensivo IO o CPU ?
¿ Funcionales (y no-SQL), objectivo contrario a lenguajes dinámicos ?
Actors ? transactional memory ? "let it crash" ? Monads ?
"SETI@home has been a success,obviously not in finding aliens,but in demonstrating the potential of large-scale distributed computing"
Funcionales, recursión, complejidad, O(N), ... def to_fib_recursive return self if zero? or one? (self-1).to_fib_recursive + (self-2).to_fib_recursive end
def to_fib_tail return self if zero? or one? first,second = 0,1 (self-1).times do first,second = second, first + second end second end
def to_fib_inject return self if zero? or one? fib = (self-1).times.inject( [0,1] ) do | group, n | [ group[1], group[0] + group[1] ] end fib[1] end
def to_fib_formula
( ( (GOLDEN_RATIO**self) - ((-GOLDEN_RATIO)**(-self)) ) / ROOT_FIVE ).to_i end
def one? self == 1 end
ROOT_FIVE = Math.sqrt(5) GOLDEN_RATIO = (1 + ROOT_FIVE) / 2
end
No olvidemos...
• IDE y herramientas• Rendimiento• Chequeos compilación• Independencia plataforma
o Lenguajeo Librerías
• Muy bueno para frameworks genéricos (Rails, RSpec, Cucumber, etc.) pero para APIs ni interfaces ni tipos
• Los dinámicos son de verdad más productivos ?
¡WARNING!
La siguiente sección contiene opiniones subversivas que pueden herir la
sensibilidad
Tipos de tipados :-)
¿Se realiza chequeo de tipos?• Sí -> Tipado fuerte• No -> Tipado débil
¿Cuándo se realiza el chequeo de tipos?• En tiempo de ejecución -> Tipado dinámico• En tiempo de compilación -> Tipado
estático
Tipos de tipados :-)
¿Java, Scala, C#...? Tipado fuerte y estático
¿JS, Ruby ....? Tipado fuerte y dinámico
¿C, C++? Tipado débil (al menos en mis tiempos)
El compilador es tonto
• Necesita información extra para chequear los tiposo Ruido sintáctico -> Boilerplate codeo Dependencia del IDE (CTRL+Space)o Reza para que sea rápido (ADA, ¿Scala?)
• Los errores de tipos son los sencillos
• Es incapaz de detectar los errores semánticos, los realmente complicados
Enter TDD (que cansino...)
• No existe técnica para resolver el problema de detección de errores funcionales al 100%
• Pero TDD y BDD son muy efectivos!
Enter TDD (que cansino...)
• No existe técnica para resolver el problema de detección de errores funcionales al 100%
• Pero TDD y BDD son muy efectivos!
Ahora os cuento un secreto....
Enter TDD (que cansino...)
• No existe técnica para resolver el problema de detección de errores funcionales al 100%
• Pero TDD y BDD son muy efectivos!
Ahora os cuento un secreto....
¡ TDD también detecta errores sintácticos y de tipos !
¡ Y los detecta al 100 % !(al menos en mi experiencia)
Tipado dinámico wins!
Invocando el principio KISS:
IF ( "TDD lo necesito sí o sí" && "TDD detecta errores de tipado y de sintaxis") { DO Eliminar compilador}
Y de paso elimino el ruido sintáctico:
• Escribo menos código• El código es más legible• Ciclo de desarrollo más ágil
Tipado estático strikes back
• Inferencia de tipos• Tipado estructural
¿Tanta complejidad hará que el compilador sea lento?
¿Realmente que me aporta?¿Qué gano respecto a un lenguaje dinámico
usando TDD?
¿Y los DSL?
• Hacen tu código más cercano al dominio del problema
• Legibilidad aumenta• Lenguajes dinámicos son buenos para esto
o Son maleableso Muchas capacidades de metaprogramacióno ¡Ruby y Groovy!
• Los lenguajes estáticos normalmente son malos para esto: JMock Vs. Spock Vs. RSpec Vs. Jasmine
• ! Pero Scala es muy bueno !
¡YA ME VOY!
Y ahora pasemos a las conclusiones(Gracias Leo!)
Conclusiones
- ¿ Dinámicos para equipos pequeños con gente muy buena ?
- ¿ Estáticos para proyectos grandes o con varios equipos ?
- ¿ Funcionales para problemas específicos ?
Kent Beck: "Simplemente cuenta la historia. No me gustan las conclusiones morales al final de las historias."
Así que cada uno saque sus propias conclusiones :-)
Muchas gracias Leo Antoli @lantoliEnrique Amodeo @eamodeorubio