如何避免编写一个糟糕的代码_简单与容易:编写通用代码以避免重复(表示要导入的数据)...

如何避免编写一个糟糕的代码

在用于数据导入的批处理作业中,我们有许多类似的类用于保存要导入的数据。 从技术上讲,它们都是不同的,具有不同的领域,但从概念上讲,它们是相同的。 我发现这种概念上的重复令人不快,并编写了一个更通用的类来替换所有这些。

重构的灵感来自Clojure及其对通用结构的偏爱,例如具有许多功能的地图,而不是针对特定案例的数据结构(即类)的OO方式,例如在本次Rich Hickey访谈中解释的那样,“ OO可以严重阻碍重用”。

原始代码:

public interface LogEntry {
     Date getTimestamp();
     /** For dumping into a .tsv file to be imported into Hadoop */
     String toTabDelimitedString();
     String getSearchTerms();
     boolean hasSearchTerms();
     String[][] getColumns();
     String getKey();
     void mergeWith(LogEntry entry);
}
// Another impl. of LogEntry, to showcase the conceptual and, 
// to a degree, factual duplication
public class PlayerEventLogEntry implements LogEntry {
    
    public static final String[][] COLUMNS = { ... }
    
    private String userId;
    private String contentId;
    private String sessionId;
    ...
    
    public PlayerEventLogEntry(...) { /* assign all fields ... */ }

    @Override
    public String[][] getColumns() {
	return COLUMNS;
    }
    ...
        
	@Override
	public String toTabDelimitedString() {
		StringBuilder sb=new StringBuilder();
		sb.append(contentId);
		sb.append(FIELD_DELIMITER);
		sb.append(userId);
		sb.append(FIELD_DELIMITER);
		sb.append(sessionId);
		sb.append(FIELD_DELIMITER);
		...
		return sb.toString();
	}
}
// Called from a data import job to process individual log lines.
// Some time later, the job will call toTabDelimitedString on it.
public class PlayerEventsParser ... {

   @Override
    public LogEntry parse(String logLine) throws LogParseException {
        ... // tokenizing, processing etc. of the data ...
        return new PlayerEventLogEntry(userId, contentId, sessionId, ...);
    }
    
}
// One of 15 implementations of LogEntry for import of right granted event logs
public class RightGrantedLogEntry implements LogEntry {

	private static final char FIELD_DELIMITER = '\t';
        
    public static final String[][] COLUMNS = { { "messageId", "STRING" }
		{ "userId", "STRING" },{ "rightId", "STRING" }, ... };


	private String messageId;
	private Date timestamp;
	private String userId;
	private String rightId;
	...

	public RightGrantedLogEntry(String messageId, Date timestamp, String userId, String rightId, ...) {
		this.messageId=messageId;
		this.timestamp=timestamp;
		this.userId=userId;
		this.rightId=rightId;
		...
	}

	@Override
	public String[][] getColumns() {
		return RightGrantedLogEntry.COLUMNS;
	}

	@Override
	public String getKey() {
		return messageId;
	}

	@Override
	public String getSearchTerms() {
		return null;
	}

	@Override
	public Date getTimestamp() {
		return timestamp;
	}

	@Override
	public boolean hasSearchTerms() {
		return false;
	}

	@Override
	public void mergeWith(LogEntry arg0) {}  
	
	@Override
	public String toTabDelimitedString() {
		StringBuilder sb=new StringBuilder();
		sb.append(messageId);
		sb.append(FIELD_DELIMITER);
		sb.append(userId);
		sb.append(FIELD_DELIMITER);
		sb.append(rightId);
		sb.append(FIELD_DELIMITER);
		...
		return sb.toString();
	}
}

重构的代码,其中所有LogEntry实现都已被MapLogEntry代替:

public interface LogEntry {
    // same as before
}
// The generic LogEntry implementation
// JavaDoc removed for the sake of brevity
public class MapLogEntry implements LogEntry {
    
    private static final char FIELD_SEPARATOR = '\t';
    private Map<String, String> fields = new HashMap<String, String>();
    private final String[][] columns;
    private final StringBuilder tabString = new StringBuilder();
    private final Date timestamp;
    private String key;
    
    public MapLogEntry(Date timestamp, String[][] columns) {
        this.timestamp = checkNotNull(timestamp, "timestamp");
        this.columns = checkNotNull(columns, "columns");
    }

    @Override
    public String toTabDelimitedString() {
        return tabString.toString();
    }
    
    public MapLogEntry addInOrder(String column, String value) {
        checkAndStoreColumnValue(column, value);
        appendToTabString(value);
        return this;
    }

    public MapLogEntry validated() throws IllegalStateException {
        if (fields.size() != columns.length) {
            throw new IllegalStateException("This entry doesn't contain values for all the columns " +
                	"expected (" + columns.length + "). Actual values (" + fields.size() + "): " + toTabDelimitedString());
        }
        return this;
    }

    private void checkAndStoreColumnValue(String column, String value) {
        final int addedColumnIndex = fields.size();
        checkElementIndex(addedColumnIndex, columns.length, "Cannot add more values, all " + columns.length +
                " columns already provided; column being added: " + column);
        String expectedColumn = columns[addedColumnIndex][0];
        if (!column.equals(expectedColumn)) {
            throw new IllegalArgumentException("Cannot store value for the column '" +
                    column + "', the column expected at the current position " + addedColumnIndex +
                    " is '" + expectedColumn + "'");
        }
        fields.put(column, value);
    }

    private void appendToTabString(String value) {
        if (tabString.length() > 0) {
            tabString.append(FIELD_SEPARATOR);
        }
        tabString.append(valOrNull(replaceFildSeparators(value)));
    }
    
    /** Encode value for outputting into a tab-delimited dump. */
    Object valOrNull(Object columnValue) {
        if (columnValue == null) {
            return HiveConstants.NULL_MARKER;
        }
        return columnValue;
    }

    @Override
    public Date getTimestamp() {
        return timestamp;
    }

    @Override
    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public MapLogEntry withKey(String key) {
        setKey(key);
        return this;
    }

    /**Utility method to simplify testing. */
    public Map<String, String> asFieldsMap() {
        return fields;
    }
    ...
}
// Called from a data import job to process individual log lines.
// Some time later, the job will call toTabDelimitedString on it.
public class PlayerEventsParser ... {

   @Override
    public LogEntry parse(String logLine) throws LogParseException {
        ... // tokenizing, processing etc. of the data ...
        return new MapLogEntry(timestamp, getColumns())
                    .addInOrder("userid", userId)
                    .addInOrder("contentid", contentId)
                    .addInOrder("sessionid", sessionId)
                    ...
                    .validated();
    }
    
}

改进我们用一类替换了大约15个类,使可以在单个位置(DRY)更改将数据转换为制表符分隔的字符串的方式,并提供了一种美观,流畅的API,其用法在每个位置都非常相似。 新的MapLogEntry更加易于测试(修改所有现有类以支持MLE所做的工作将是一场噩梦)。

异议有人可能认为许多原始POJO类比一个泛型类更简单。 一个泛型类肯定比原始数据结构更复杂,但是总的来说解决方案不那么复杂,因为有更少的片段,并且单个片段在所有地方都以相同的方式使用,因此产生的认知负担较小。 前者的代码更“容易理解”,而后者则更“简单”。

干燥原理


翻译自: https://www.javacodegeeks.com/2013/06/simple-vs-easy-writing-a-generic-code-to-avoid-duplication-representation-of-data-to-import.html

如何避免编写一个糟糕的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值