quiero hacer ágil, ¿y ahora qué: java, ruby o scala?

41
Quiero hacer ágil, ¿ y ahora qué: Java, Ruby o Scala ? Leo Antoli @lantoli Conferencia Agile-Spain 2011 - Castellón

Upload: agile-spain

Post on 13-Jan-2015

660 views

Category:

Documents


1 download

DESCRIPTION

Leo Antoli

TRANSCRIPT

Page 1: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

Quiero hacer ágil, ¿ y ahora qué: Java, Ruby o Scala ?

Leo Antoli

@lantoli

Conferencia Agile-Spain 2011 - Castellón

Page 2: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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 ?

Page 3: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

Fuera de la sesión

• Lenguajes en cliente• Frameworks• Sólo se tratan algunos lenguajes

Page 4: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

Un lenguaje para dominarlos a todos

Page 5: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 6: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

PopularidadGoogle Code Jam 2011

Page 7: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

Causa/efecto o correlación

Page 8: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

Sopa de letras

Page 9: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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)

Page 10: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

Entrega continua de valor: ¿ sólo con pruebas automáticas ?

Page 11: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

¿ TDD/BDD obligatorio ? ...Anda antes de correr

Page 12: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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 ?

Page 13: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 14: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 15: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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"));   }}...

Page 16: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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...

Page 17: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 18: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 19: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 20: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

 

Page 21: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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) ....

Page 22: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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)  }}

Page 23: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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}

Page 24: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 25: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 26: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

JVM

GroovyScalaJRubyJythonClojure...

Quien lo iba a imaginar

Page 27: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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"

Page 28: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 29: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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 ?

Page 30: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

¡WARNING!

La siguiente sección contiene opiniones subversivas que pueden herir la

sensibilidad

Page 31: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 32: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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)

Page 33: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 34: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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!

Page 35: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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....

Page 36: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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)

Page 37: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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

Page 38: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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?

Page 39: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

¿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 !

Page 40: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

¡YA ME VOY!

Y ahora pasemos a las conclusiones(Gracias Leo!)

Page 41: Quiero hacer ágil, ¿y ahora qué: Java, Ruby o Scala?

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