190 likes | 210 Views
Best practices for writing efficient and clean code using object-oriented programming, polymorphism, and avoiding global variables. Learn to handle exceptions and use reflection wisely.
E N D
Solution MidTerm2017 Advanced Programming Giuseppe Attardi Università di Pisa
General Remarks • Follow good programming guidelines • eg: • https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/styleguide.md • In particular: • NEVER, EVER, EVER USE global variables:they hinder parallelism • Use exceptions sparingly (and handle them) • Use strings sparingly (StringBuffer)
Use Object Oriented Programming • USE Polymorphism, USE Polymorphismmakes your code more modular, more readable, more extensible • Avoid using Reflection (instanceof) • Avoid static methods, aka functions, in OOP • Avoid functions for similar tasks: use polymorphic method in each of the classes where the operation is needed
Bad Example class Utils { static String bytesToString(); static String inputstoString(); static String outputsToString(); … } • Alternative: • Use method serialize() in each class
Hash class Hash { private static final MessageDigest md = MessageDigest.getInstance(“SHA-256”); public byte[] code; public Hash(byte[] bytes) { code = md.digest(bytes); } boolean isValid() { return code[0] == 0 && … code[2] == 0; } boolean equals(Hash other) { return Arrays.equals(code, other.code); } }
Input class Input extends Pair<Hash, Integer> { // Hash for transaction, Integer for index in outputs public Input(Transaction t, int idx) { super(new Hash(t.serialize()), idx); } public byte[] serialize() { byte[] hash = getKey().code; byte bb = ByteBuffer.allocate(hash.length + 4); bb.put(hash).putInt(getValue()); return bb.array(); } } class Output extends Pair<Integer, PublicKey> { public Output(int v, PublicKey key) { … } public byte[] serialize() { … } }
Transaction class Transaction implements Serializable { private List<Input> inputs; private List<Output> outputs; private byte[] signature; public Transaction() { inputs = new ArrayList<>(); outputs = new ArrayList<>(); } public add(Input i) { inputs.add(i); } public add(Output o) { outputs.add(o); }
Transaction public void sign(PrivateKey key) { Signature s = new Signature(”SHA256withRSA”); s.init(key); for (Input i : inputs) s.update(i.serialize()); for (Output o : outputs) s.update(o.serialize()); signature = s.sign(); } public byte[] serialize() { byte bs = ByteArrayOutputStream(); for (Input i : inputs) bs.write(i.serialize()); for (Output o : outputs) bs.write(o.serialize()); bs.write(signature); return bs.toByteArray(); }
Block class Block implements Serializable { private int seq; private int nonce = -1; private Hash previous; private Transaction t; public Block(int seq, Hash previous, Transaction t) { … } public hash() { return new Hash(serialize()); } public int mine() { Hash hash; do { nonce++; hash = this.hash(); } while (!hash.isValid()) return nonce; }
Block public byte[] serialize() { byte bs = ByteArrayOutputStream(); bs.write(ByteBuffer.allocate(4).putInt(seq).array()); bs.write(ByteBuffer.allocate(4).putInt(nonce).array()); bs.write(previous.hash()); bs.write(t.serialize()); return bs.toByteArray(); }
BlockChain class BlockChain { private List<Block> chain = new LinkedList<>();
boolean isValidBlockChain() { Map<Hash, BitSet> spentOutputs = new HashMap<>(); Hash current = null; // current block in chain for (Block next : chain) { // next block in chain Transaction tx = next.transaction; if (current == null) { spentOutputs.put(new Hash(tx.serialize()), new BitSet(tx.getOutputs().size())); current = next; continue; } Hash previous = current.previous(); Hash hash = next.hash(); if (!hash.isValid() || !hash.equals(previous)) // current block must refer to next one return false; for (Input input : tx.getInputs()) { if (spentOutputs.computeIfPresent(input.getKey(), // update spentOutputs (k, v) -> { if (v.get(input.getValue())) // already spent output at index input.getValue() return null; v.set(input.getValue()); // update return v; // put back into Map }) == null) return false; } spentOutputs.put(new Hash(tx.serialize()), new BitSet(tx.getOutputs().size())); current = next; } return true; }
public int getBalance(PublicKey user) { Map<Hash, BitSet> spentOutputs = new HashMap<>(); Iterator <Block> it = chain.descendingIterator(); // from last to first while (it.hasNext()) { Block b = it.next(); Transaction tx = b.transaction(); Hash hash = new Hash(tx); List<Output> outputs = tx.getOutputs(); for (int i = 0; i < outputs.size(); i++) { Output o = outputs.get(i); if (user.equals(o.getValue()) && (spentOutputs.get(hash) == null || !spentOutputs.get(hash).get(i))) balance += o.getKey(); } // now add the outputs to the map spentOutputs for (Input in : tx.getInputs()) spentOutputs.computeIfAbsent(in.getKey(), v -> new BitSet()).set(in.getValue())); } return balance; }
public static void main(String args[]) { String input; String[] tokens; Scanner reader = new Scanner(System.in); init(); do { do { System.out.print("> "); input = reader.nextLine().trim(); } while (input.equals("")); tokens = input.split("\\s+"); switch (tokens[0]) { case "status": printBlockChain(); break; case "check": if (chain.isValidBlockChain()) System.out.println("The block chain is valid!"); else System.out.println("The block chain is not valid."); break;
A closure is a functional object that consists of a function and the lexical environment at the time of its creation. The environment contains the bindings for the non local variables visible in the scope of the function. • In C#, a delegate type is a type that represents functions with a given signature. • A delegate instance is created on an object and a method on that object compatible with its signature. • A delegate instance can be used as a function by invoking it with arguments, and this corresponds to invoking the delegate method on the delegate target with the given arguments.